对象的本质
在main中新建一个Teacher类,cd到当前的main.m文件,输入命令clang -rewrite-objc xxx.m ,如果此时报以下错误
请重新输入
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m此时就得到了一个.cpp文件,打开搜索直接定位到Teacher
struct Teacher_IMPL {
struct NSObject_IMPL NSObject_IVARS;
};
我们发现对象在底层的本质就是结构体,为了看的更清晰,我们在Teacher中新增一个name属性,重新生成.cpp文件,得到
struct Teacher_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_name;
};
我们发现有一个隐藏属性NSObject_IMPL,全局搜索查看下,得到原来是一个isa
struct NSObject_IMPL {
Class isa;
};
同时我们也发现了这些typedef
typedef struct objc_class *Class;
typedef struct objc_object *id;
typedef struct objc_object Teacher;
可以推出OC中NSObject在C++的底层其实就是objc_object;OC中的Class在底层是objc_class; 我们也看到了Teacher的set和get方法,发现了两个隐藏参数cls和_cmd
static NSString * _I_Teacher_name(Teacher * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Teacher$_name)); }
static void _I_Teacher_setName_(Teacher * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_Teacher$_name)) = name; }
(char *)self + OBJC_IVAR_$_Teacher$_name))首地址+平移
nonPointerIsa分析
上篇文章讲到了在alloc开辟内存空间之后,有一个isa和类的绑定obj->initIsa(cls),顺着方法链走,我们找到了一个isa_t,它是一个联合体union
union isa_t {
isa_t() { } //构造方法
isa_t(uintptr_t value) : bits(value) { } //构造方法
uintptr_t bits;
private:
// Accessing the class requires custom ptrauth operations, so
// force clients to go through setClass/getClass by making this
// private.
Class cls;
public:
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
...
#endif
...
};
我们知道指针占用8字节也就是64位,这里64位不单单只是一个指针,实际上存储的也有记录是否正在释放、weak、引用计数、关联对象、析构函数这些都是和类息息相关的东西。所以就出现了nonPointerIsa,它不在是一个简简单单的指针,要看里面具体64位有什么,只有ISA_BITFIELD与之相关,找到它在__x86_64__下的定义
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
相加刚好等于64位,默认创建的类都是nonPointerIsa,这里面的东西跟类最相关的是shiftcls,回到上级继续看obj->initIsa(cls)方法,如果是!nonpointer,直接赋值,如果是nonpointer需要给联合体里面的位域赋值(也就是上面介绍的ISA_BITFIELD ),默认生成的类都是nonpointer,赋值的方法newisa.setClass(cls, this);
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
isa_t newisa(0);
if (!nonpointer) {
newisa.setClass(cls, this);
} else {
...
#if SUPPORT_INDEXED_ISA
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
# if ISA_HAS_CXX_DTOR_BIT
newisa.has_cxx_dtor = hasCxxDtor;
# endif
newisa.setClass(cls, this);
#endif
newisa.extra_rc = 1;
}
isa = newisa;
}
isa的赋值过程
首先我们知道x/4gx p的isa(0x011d800100008275)指向当前的类信息,通过p/x Person.class我们得到当前的类地址(0x0000000100008270),那么这个指向的过程是怎么设计的呢?
这里就用到了上面提的ISA_BITFIELD,我们知道iOS是小端模式,ISA_BITFIELD的排列方式如下,要得到shiftcls需要右移3位,左移20位,再右移17位就能拿到。
我们代码来验证一下:
补充
noopointer: 表示是否对isa指针开启指针优化,0:纯isa指针,1:不⽌是类对象地址,isa 中包含了类信息、对象的引⽤计数等 has_assoc:关联对象标志位,0没有,1存在 has_cxx_dtor:该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象 shiftcls: 存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位⽤来存储类指针。 magic:⽤于调试器判断当前对象是真的对象还是没有初始化的空间 weakly_referenced:对象是否被指向或者曾经指向⼀个 ARC 的弱变量, 没有弱引⽤的对象可以更快释放。 deallocating:标志对象是否正在释放内存 has_sidetable_rc:当对象引⽤计数⼤于 10 时,则需要借⽤该变量存储进位 extra_rc:当表示该对象的引⽤计数值,实际上是引⽤计数值减 1, 例如,如果对象的引⽤计数为 10,那么 extra_rc 为 9。如果引⽤计数⼤于 10, 则需要使⽤到 has_sidetable_rc。