主动已经是我对热爱东西表达的极限了
对象的本质?
联合体位域的简析?
isa的结构信息?
isa如何关联类?
通过位运算验证关联类
总结。
什么是对象?
对象在底层变成了什么呢?-
什么是Clang?
Clang是一个由Apple主导编写,基于LLVM的C/C++/Objective-C编译器
-
用Clang做些什么?
Clang 通过底层编译,将一些m文件编译为cpp。 因为OC为C++或者C的超集,通过Clang底层编译,可以更多的看到底层的实现原理与逻辑和底层的架构
-
如何使用Clang?
Clang
终端操作四种命令如下:
clang -rewrite-objc main.m -o main.cpp 把目标文件编译成c++文件
`xcode`安装的时候顺带安装了`xcrun`命令,`xcrun`命令在`clang`的基础上进行了 一些封装,要更好用一些
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp (模拟器)
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- arm64.cpp (手机)
Clang 具体操作请查看:后续
通过Clang
编译查看类
的底层是如何实现的,首先定义如下:
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation LGPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!"); }
return 0;
}
找到已经编译好的main.cpp
文件,搜索LGPerson
,得到我想要的信息如下:
#ifndef _REWRITER_typedef_LGPerson
#define _REWRITER_typedef_LGPerson
typedef struct objc_object LGPerson;
typedef struct {} _objc_exc_LGPerson;
#endif
extern "C" unsigned long OBJC_IVAR_$_LGPerson$_name;
struct LGPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_name;
};
// @property (nonatomic, copy) NSString *name;
/* @end */
// @implementation LGPerson
static NSString * _I_LGPerson_name(LGPerson * self, SEL _cmd) {
return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name));
}
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name) {
objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _name), (id)name, 0, 1);
}
// @end
-
对象
在底层会被编译为struct
那对象
是如何被编译为结构体
的呢?
我们知道结构体在C++可以继承,C可以伪继承
伪继承方式:
直接将NSObject
结构体定义为LGPerson
中的第一个属性
,意味着LGPerson
拥有NSObject
中的所有成员变量
。
LGPerson
中的第一个属性NSObject_IVARS
等效于NSObject中
的isa
这里将LGPerson_IMPL
结构体中的第一属性
命名为当前结构体,即LGPerson_IMPL
拥有 NSObject
中的所有成员变量
。而NSObject_IVARS
即isa
而LGPerson
类中的name
的get, set
方法也是一一对应的:
而set
方法调用了objc_setProperty
,通过分析得出objc_setProperty
采用工厂模式:return newValue
, remove oldValue
;意味着任何类中的set
方法都会执行了如下操作:
继续分析isa结构,OC底层原理 结构体&联合体
-
isa结构分析:
在之前探索 OC底层原理 alloc & init & new 篇 时提到过initInstanceIsa
方法,通过initInstanceIsa
为切入点来分析isa
是如何初始化的,可以看到isa
指针的类型isa_t
定义是通过联合体(union)来定义
,联合体
不清楚的小伙伴请参考:OC底层原理 结构体&联合体
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls; //这里返回的cls 为什么会是一个class类型?请查看后续
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
这里的isa_t
为什么要定义成联合体
呢?是为了优化内存
,而isa
指针占用的内存大小为8
字节,即64
位,已经能够存储大量信息了,这样可以节省内存空间,以提高性能的目的
而isa_t
的定义中可以看出,
通过cls
初始化,bits
无默认值
通过bits
初始化,cls
有默认值
体现了联合体
的互斥性
通过宏ISA_BITFIELD
可以看出isa
在iOS( __arm64__)
和macOS(__x86_64__)
的计算是存在差异
的
-
isa
的结构信息对应如下:nonpointer
:表示是否对isa
指针开启指针优化
0
:纯isa
指针;
1
:不止是类对象地址,isa
中包含了类信息、对象的引用计数等has_assoc
:关联对象标志位,
0
没有关联对象
1
存在关联对象has_cxx_dtor
:该对象是否有 C++ 或者 Objc 的析构器(类似于dealloc
)
如果有
析构函数,则需要做析构逻辑
如果没有
,则可以更快的释放
对象shiftcls:
存储类指针的值(类的地址
), 即类信息
。开启指针优化
的情况下,
在__arm64__
架构中有33
位用来存储类指针。
在__x86_64__
架构中有44
位用来存储类指针。magic
:用于调试器判断当前对象是真的对象
还是没有初始化的空间
weakly_referenced
:志对象是否被指向
或者曾经指向一个 ARC 的弱变量
没有弱引用的对象
可以更快释放。deallocating
:标志对象是否正在释放
内存has_sidetable_rc
:当对象引用计数大于 10
时,则需要借用该变量存储进位
extra_rc
:当表示该对象的引用计数值
,实际上是引用计数值减 1
, 例如,如果对象的引用计数为 10
,那么extra_rc
为9
。如果引用计数大于 10
, 则需要使用has_sidetable_rc
更直观的isa
的结构信息图如下:
通过 initIsa
方法,查看 isa
指针是如何被初始化
的
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
if (!nonpointer) {
isa = isa_t((uintptr_t)cls);//isa初始化
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
isa_t newisa(0); //isa初始化
#if SUPPORT_INDEXED_ISA // !nonpinter 执行,即isa通过cls定义
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else // bits 时执行的流程
newisa.bits = ISA_MAGIC_VALUE; // bits进行赋值
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3; //isa与类关联
#endif
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
}
-
initIsa
方法主要使用:cls
初始化isa
,和bits
初始化isa
执行代码,开始验证,以LGPerson
为例:
对赋值newisa.bits
赋值之后打印出newisa
查看LLDB结果
- 对赋值
newisa.bits
赋值会对cls
进行追加;对cls
赋值不会同时对bits
进行赋值(联合体互斥性
)
通过LLDB打印结果,在进行赋值的时候,bits
中的magic
会存在一个59
呢?
使用计算器查看宏ISA_MAGIC_VALUE
与59
的2进制
magic
的占位为6
,从47~52
表示magic
的占位,所以magic
的默认值为59
,二进制
表示;也可以理解为isa
指针将magic
的占位为6
,转换为2进制
存储
- isa与类关联
当执行完如下代码后,LLDB打印出当前newisa
,
cls
与 isa
关联原理就是isa
指针中的shiftcls
位域中存储了类信息
,其中initInstanceIsa
的过程是将 calloc
指针 和当前的 类cls
关联起来。而newisa.shiftcls = (uintptr_t)cls >> 3
是将当前cls 强转
为系统能够编译的0
,1
识别码。然后右移3
位。之所以要移动3位,要知道shiftcls
才是我们需要存储的类信息
,从上面isa对照表
中可以看出,前面三位并不是我们需要的类内容
,需要右移3位
进行抹0
操作。
newisa.shiftcls = (uintptr_t)cls >> 3; //isa与类关联
这个时候我们已经知道isa
与类
进行了关联。
-
开始验证
isa
中的shiftcls
是否真正的存储了当前类信息
将initInstanceIsa
方法返回出去,然后开始验证:
-
方式1. 通过
isa
指针地址与宏ISA_MSAK
的值 通过位运算&
来验证
LLDB 操作:# define ISA_MASK 0x00007ffffffffff8ULL //__x86_64__ # define ISA_MASK 0x0000000ffffffff8ULL //__arm64__
po obj
,然后x/4gx LGPerson
的地址 或者直接x/4gx
,然后取LGPerson
的isa
地址指针&
宏ISA_MASK
,结果如下:
-
方式2. 使用位运算验证
shiftcls
存储在当前3~46
位,
向右
移动3
位,抹除0~2
号位数据,
向左
移动20
位,移除47~64
号位的数据,
向右
移动17
位,回到原来的shiftcls
所在的存储位,直接取出,即LGPerson 类信息
-
方式3. 通过
在runtime
的object_getClass
来验证main.m
导入头文件#import
,然后直接打印出当前的类也能够验证,这里就不打印了,直接去查看object_getClass
的实现,
object_getClass
方法如下:
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
我们进入getIsa
方法继续查看,
inline Class
objc_object::getIsa()
{
if (fastpath(!isTaggedPointer())) return ISA(); //直接return了当前isa
extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
uintptr_t slot, ptr = (uintptr_t)this;
Class cls;
slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
cls = objc_tag_classes[slot];
if (slowpath(cls == (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer)) {
slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
cls = objc_tag_ext_classes[slot];
}
return cls;
}
继续查看ISA()
做了哪些事情,
inline Class
objc_object::ISA()
{
ASSERT(!isTaggedPointer());
#if SUPPORT_INDEXED_ISA
if (isa.nonpointer) {
uintptr_t slot = isa.indexcls;
return classForIndex((unsigned)slot);
}
return (Class)isa.bits; //直接返回
#else
return (Class)(isa.bits & ISA_MASK); //这里与我们方式1做的操作是一样的
#endif
}
我们可以看出object_getClass
的内部实现,也是采用了位运算&
的方式对当前类进行处理。
验证总结:验证isa与类是否关联还有其他方式,这里不一一阐述,有兴趣的同学可以自己探索。
-
isa结构分析总结:
1.isa
通过isa_t
方法进行初始化,其中使用到了联合体位域
;isa
与类
关联的同时,做了强转
的操作和位运算计算存储
,其中newisa.shiftcls = (uintptr_t)cls >> 3;
的uintptr_t
表示long
类型。
2.x86_64
和arm64
环境不同,存在部分计算差异
3.掌握如何对类
和isa
的关联验证
4.如何使用Clang
查看源码。