写在前面
首先我们在查看objc
源码的时候,可以看到每个objc_object
的结构里面有一个isa
属性;另外我们都知道isa
关系着OC
对象的命脉,接下来就开始探究。
isa
结构
/// isa 的联合体结构
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls; // 指向具体的类对象,关联类的关键
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
/// ISA_BITFIELD 位域属性
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# 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 deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
# else
# error unknown architecture for packed isa
# endif
从结构定义可以看出isa
是一个联合体,并且里面还使用了位域计算的特点,使得结构更加精简、方便理解、内存最大利用化。在不同的架构下面位域计算(告诉位置的区域)所占的内存大小不同,区别主要在于shiftcls
(arm64 真机架构下占33位,x86 模拟器下占44位) 和 extra_rc
(arm64 真机架构下占19位,x86 模拟器下占11位); 但是不管是什么架构下所占的整个大小都是64位 == 8字节。关于联合体和结构体的区别以及位域的知识点,请自行了解;个人理解联合体和结构体的区别在于所有成员公用一块内存,内存的大小取决于联合体中最大成员的所占用内存大小,并且同一时刻的成员是互斥的,不会存在两个成员,isa这里最大是8个字节,就是64位。位域存储的值定义为了优化内存,因为否则用很多属性取定义位域存储的类信息则需要消耗大量的内存。
具体isa
中的位域存储的到底是什么呢?
从源码仲可以看到有一个标识nonpointer
来区分isa
的类型;nonpointer
表示当前的isa
除了存储isa
结构之外还存储了一些类的信息,对象的引用计数等;目前大部分的isa
都不是nonpointer
类型的isa
, 都是纯isa
指针;所以我们按照这个方向取去分析源码流程, 这里以__arm64__
架构下的🌰:
属性 | 占用字节数 | 说明 |
---|---|---|
nonpointer | 1 | 表示是否对isa开启指针优化;0 - 未优化,纯isa指针结构,1 - 优化,不仅包含类对象的地址,isa还存储类信息,对象的引用计数等 |
has_assoc | 1 | 关联对象的标识位,1 - 存在对象管理,0 - 不存在 |
has_cxx_dtor | 1 | 当前对象是否含有 C++ 或者 Objc 的析构器,如果存在析构函数,则需要执行析构函数逻辑,如果没有,则可以在释放的时候直接更快的释放对象 |
shiftcls | 33 | 重点: 存储类指针的值。开启指针优化的情况下,在 arm64 下33位存储类指针 |
magic | 6 | 用于调试器判断当前对象是真的对象还是没有初始化的内存空间 |
weakly_referenced | 1 | 指当前对象是否被指向或者曾经指向一个ARC环境下的变量(是否存在弱引用),如果不存在可以弱引用的对象则可以更快的释放当前对象 |
deallocating | 1 | 标志当前对象是否正在释放内存 |
has_sidetable_rc | 1 | 当对象的引用计数大于10时(有时候引用计数会存储在 isa 中,通常在 SideTable 散列表中); 则需要借用改变量来存储进位 |
extra_rc | 19 | 表示当前对象的引用计数值,实际上引用计数值因为为1;例如,如果当前的引用计数为10,那么 extra_rc = 9。如果引用计数大于10,则需要使用上面的 has_sidetable_rc 存储 |
isa
初始化
在前面分析alloc
流程中分析到callAlloc
方法;然后调用class_createInstance
方法,最后逻辑到obj->initInstanceIsa(cls, hasCxxDtor)
; 顾名思义,该方法就是初始化类的isa
的入口。
- objc_object::initInstanceIsa
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
assert(!cls->instancesRequireRawIsa());
assert(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
- objc_object::initIsa
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
assert(!isTaggedPointer());
if (!nonpointer) {
isa.cls = cls; // 走当前分支
} else {
assert(!DisableNonpointerIsa);
assert(!cls->instancesRequireRawIsa());
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
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
newisa.bits = ISA_MAGIC_VALUE;
// 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;
#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;
}
}
isa
原理
关联类
- 对象在内存中可能会因为属性的内存对齐进行优化,所以对象的属性可能不是固定的,但是对象的第一个属性必然是isa指针,👇进行验证。
run-time的一个api ->
object_getClass
方法可以获取当前对象的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); // Class 等于 isa指针的bits(即isa) 与上一个mask值,类似于子网掩码的效果,盖住不需要的数据,只展示想要的数据
#endif
}
因此我们通过拿到class类对象的地址,以及isa的地址进行mask与运算,去验证isa和类关联的事实。
对象和类对象
- 对象
- 开辟内存创建的实例对象
- 开发者根据类实例化出的结果,
alloc
所开辟的内存对象 - 类可以创建多个
- 类对象
- 一个类结构的对象
- 代码定义的类,系统创建的对象
- 内存只有一份
// 创建两个对象,打印内存地址以及类地址
MRObject *obj1 = [MRObject alloc];
MRObject *obj2 = [MRObject alloc];
// lldb 调试打印
(lldb) x/4gx obj1 // 打印obj1的内存段 0x001d800100001269 isa
0x101a282c0: 0x001d800100001269 0x0000000000000000
0x101a282d0: 0x0000000080080000 0x00007fff916afa68
(lldb) x/4gx obj2 // 打印obj2的内存段 0x001d800100001269 isa
0x101a20500: 0x001d800100001269 0x0000000000000000
0x101a20510: 0x636f72504b575b2d 0x70756f7247737365
// 以上两个结果可以看出两个对象的地址不同,但是isa地址是相同的,而isa是关联类的 => object_getClass 中得出 isa & mask => 类对象的地址
(lldb) p/x MRObject.class // 打印MRObject类对象的地址 0x0000000100001268
(Class) $4 = 0x0000000100001268 MRObject
// 通过上面的isa & mask(_x86_ = 0x00007ffffffffff8) 得到 0x0000000100001268 和类地址相同
(lldb) p/x 0x001d800100001269 & 0x00007ffffffffff8
(long) $5 = 0x0000000100001268
// 类地址打印的结果即为MRObject类,所以类对象是只有一份的!
(lldb) po 0x0000000100001268
MRObject
isa
走向、类继承
类、元类、根源类
- 类 class
- 代码构造的类,系统创建的对象
- 元类 meta_class
- 类的isa指向的虚拟类,系统创建的;在编译阶段,发现存在类对象,为了方便存储类方法信息等,创建的该类的元类对象
- 根元类 root_meta_class
- NSObject 的元类,由于 NSObject 是所有类的根类
类 -> 元类 -> 根元类
isa 走向图
苹果爸爸官方权威的经典isa
走位图👇👇
调试验证:
MRObject 继承自 NSObject
void testMRObject() {
// MRObject实例对象
NSObject *object1 = [MRObject alloc];
// MRObject类
Class class = object_getClass(object1);
// MRObject元类
Class metaClass = object_getClass(class);
// MRObject根元类
Class rootMetaClass = object_getClass(metaClass);
// MRObject根根元类
Class rootRootMetaClass = object_getClass(rootMetaClass);
NSLog(@"MRObject isa -----------------\n%p 实例对象\n%p 类\n%p 元类\n%p 根元类\n%p 根根元类",object1,class,metaClass,rootMetaClass,rootRootMetaClass);
}
void testNSObject() {
// NSObject实例对象
NSObject *object1 = [NSObject alloc];
// NSObject类
Class class = object_getClass(object1);
// NSObject元类
Class metaClass = object_getClass(class);
// NSObject根元类
Class rootMetaClass = object_getClass(metaClass);
// NSObject根根元类
Class rootRootMetaClass = object_getClass(rootMetaClass);
NSLog(@"NSObject isa -----------------\n%p 实例对象\n%p 类\n%p 元类\n%p 根元类\n%p 根根元类",object1,class,metaClass,rootMetaClass,rootRootMetaClass);
}
NSLog
NSObject isa -----------------
0x10183e7d0 实例对象
0x100b35140 类
0x100b350f0 元类
0x100b350f0 根元类 [NSObject的元类也是根元类]
0x100b350f0 根根元类 == 根元类的isa [说明根元类isa指向自己]
MRObject isa -----------------
0x10183e3b0 实例对象
0x100001298 类
0x100001270 元类
0x100b350f0 根元类 == NSObject元类 [说明根类是NSObject]
0x100b350f0 根根元类
-
继承
- 子类 superClass -> 父类 -> 根类(NSObject)
- 子元类 superClass -> 父元类 -> 根元类(NSObject的元类)
- !!! 根元类 -> 根类 (NSObject) 形成闭环
-
isa 指向
- 实例对象(xxxInstance) isa -> 类对象(xxxClass) isa
- 类对象(xxxClass) isa -> 元类对象(xxxmetaClass) isa
- 元类对象(xxxmetaClass) isa -> 根元类对象(NSObject_meta_Class) isa
- 根元类对象(NSObject_meta_Class) isa -> 根元类对象(形成闭环)
总结
万物皆对象,isa 指向贯穿 OC 整个对象和类的线索,包括类、KVO、方法的查找流程等;都是依靠isa这条基线做的处理,所以了解本质是尤其重要的,持续学习~