前言
isa
的引出
1. 从内存段分析
iOS 底层探索篇 —— 内存字节对齐分析这篇文章中,我们通过lldb
调试的时候,第一个内存段我们并不是直接打印po
出来的,而是po
0x00000001029570d0 & 0x0000000ffffffff8
这样的操作,来打印出来的对象。
0x00000001029570d0
这片内存段其实就是isa
。0x0000000ffffffff8
这个值就是ISA_MASK
掩码的值。
2. 从对象的本质分析
iOS 底层探索篇 —— 对象的本质这篇文章中,我们知道了对象的本质就是结构体,通过继承关系找到父类在底层就是objc_objcet
的成员isa
。
isa
的初始化
1. 初始化流程
iOS 底层探索篇 —— alloc、init、new的探索这篇文章中,我们知道会调用一个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
...省略无用代码...
isa = newisa;
}
}
- 非
nonpointer
,就直接赋值cls
。- 是
nonpointer
就会做一些初始化的赋值。
2. isa
数据类型分析
union isa_t {
//两个默认的构造函数
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
//isa指向的类
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
//位域
ISA_BITFIELD; // defined in isa.h
};
#endif
};
union
联合体,一种数据类型,所占用8个字节。- 联合体的特性:内存共用,或者说带有
互斥
特性,意思就是赋值了cls
,就不对其他成员赋值了。
分析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_BITFIELD
里面的字段field
是相同的,但是在不同的架构平台下,每个field
对应的位域会有不同。下面介绍每个field
的意思
nonpointer
表示是否对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,则该变量的值为9,若超过10,则需要用到has_sidetable_rc
。
isa
的指向分析
1. 对象与类的关联
XDPerson *person = [XDPerson alloc];
1.1 通过控制台调试
简单介绍一下
lldb
命令
x/4gx
objc
打印objc
的4段内存信息。扩展:x/6gx
就是打印6段内存信息。p/t
p/t 打印二进制信息;p/o
打印八进制信息;p/x
打印十六进制信息;
从ISA_BITFIELD
里面的shiftCls
我们了解到是存储类的指针的。
(lldb) x/4gx person //打印对象person的内存信息
0x1018482f0: 0x001d800100001129 0x0000000000000000
0x101848300: 0x00000001018483d0 0x0000000101848610
(lldb) p/t XDPerson.class //打印XDPerson的二进制值
(Class) $1 = 0b0000000000000000000000000000000100000000000000000001000100101000 XDPerson
(lldb) p/t 0x001d800100001129 //person的isa的二进制值
(long) $2 = 0b0000000000011101100000000000000100000000000000000001000100101001
(lldb) p/t $2>>3<<3 //shiftcl 前面有三位 我们需要右移3位 然后左移还原位置
(long) $3 = 0b0000000000011101100000000000000100000000000000000001000100101000
(lldb) p/t $3<<17>>17 //因为是模拟器_x86_64 shiftcl后面还有17位 故先左移17位找到 然后右移17位还原位置
(long) $4 = 0b0000000000000000000000000000000100000000000000000001000100101000
- 通过上面的调试我们会发现
$1
和$4
的值是相同的,验证了对象person
的isa
是和类XDPerson
关联上了。
1.2 通过objc_getClass()
调试
这里提供给一个objc
的源码
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
inline Class
objc_object::getIsa()
{
if (!isTaggedPointer()) return ISA();
uintptr_t ptr = (uintptr_t)this;
if (isExtTaggedPointer()) {
uintptr_t slot =
(ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
return objc_tag_ext_classes[slot];
} else {
uintptr_t slot =
(ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
return objc_tag_classes[slot];
}
}
我们知道objc
都是!isTaggedPointer
的,可以直接定位到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);
#endif
}
SUPPORT_INDEXED_ISA
这个宏定义在iOS
的平台是0.
- 可以直接知道
isa.bits & ISA_MASK
这么一个&
运算来获取的。- 同时我们也可以了解
objc->getIsa()
返回的就是Class
。
下面我们通过lldb
调试直接定位到cls
。
(lldb) x/4gx person //打印对象的内存信息
0x101d047e0: 0x001d800100001129 0x0000000000000000
0x101d047f0: 0x00007fff9b5ff170 0x00000000c3000002
(lldb) p/x XDPerson.class //打印类的地址
(Class) $1 = 0x0000000100001128 XDPerson
(lldb) p/x 0x001d800100001129 & 0x0000000ffffffff8 //通过对象的isa & ISA_MASK
(long) $2 = 0x0000000100001128
直接可以观察到
$1
和$2
的值是相同的,也验证了对象的isa
指向了类。
2. isa
的在类和元类之间的游走
2.1 类在底层的本质
这里先提供给一个源码,用来描述类的本质在底层是objc_class
,同时也继承自objc_object
,说明objc_class
第一个成员也是isa
。
typedef struct objc_class *Class;
struct objc_class : objc_object{};
2.1 isa
的继续走位
我们已经了解到了对象的isa
指向了类,从而来绑定对象与类。那么类的isa
又会怎么走呢。
我们继续通过lldb
来调试。
XDPerson *person = [XDPerson alloc];
isa
从对象指向类
(lldb) x/4gx person
0x10185eac0: 0x001d8001000011a9 0x0000000000000000
0x10185ead0: 0x000000010185eba0 0x000000010185ede0
(lldb) p/x 0x001d8001000011a9 & 0x0000000ffffffff8
(long) $1 = 0x00000001000011a8
(lldb) po $1
XDPerson
我们打印
person
的内存信息 通过对象的isa & ISA_MASK
获取到了类的信息。即isa
从类的实例对象person
指向了类XDPerson
。
isa
从类指向元类
(lldb) x/4gx $1
0x1000011a8: 0x001d800100001181 0x00000001000011f8
0x1000011b8: 0x00000001003a1e50 0x0000000000000000
(lldb) p/x 0x001d800100001181 & 0x0000000ffffffff8
(long) $2 = 0x0000000100001180
(lldb) po $2
XDPerson
我们打印类
XDPerson
的内存信息,通过类的isa&ISA_MASK
获取到了另外一个类的信息。即isa
从类XDPerson
又指向了类XDPerson
(其实这个类是我们XDPerson
的metaClass
元类,它与第一步的XDPerson
的内存地址并不同)。我们编译器会把一个类作为它的元类的实例化对象来处理,就像一个对象从类实例化出来的模式。
isa
从元类指向根元类
(lldb) x/4gx $2
0x100001180: 0x001d800100aff0f1 0x00000001000011d0
0x100001190: 0x0000000101e142b0 0x0000000100000007
(lldb) p/x 0x001d800100aff0f1 & 0x0000000ffffffff8
(long) $3 = 0x0000000100aff0f0
(lldb) po $3
NSObject
我们打印元类
XDPerson
的内存信息 通过元类的isa & ISA_MASK
获取到了另外一个元类类NSObject
的信息。即isa
从元类XDPerson
又指向了元类NSObject
。这里我们可以来打印NSObjcr.class
来观察,元类NSObject
的内存地址NSObjcr.class
的内存地址并不同。
isa
从根元类指向根根元类
(lldb) x/4gx $3
0x100aff0f0: 0x001d800100aff0f1 0x0000000100aff140
0x100aff100: 0x0000000101e146e0 0x0000000300000007
(lldb) p/x 0x001d800100aff0f1 & 0x0000000ffffffff8
(long) $4 = 0x0000000100aff0f0
(lldb) po $4
NSObject
我们打印元类
NSObject
的内存信息,通过元类的isa & ISA_MASK
获取到了根元类NSObject
的信息。即isa
从元类NSObject
指向了根元类NSObject
。其实做到这里我们就可以看到了 第三步的NSObject与第四步的NSObject是同一片内存地址。
通过上面的lldb调试我们基本上了解到了isa的指向走位分析,下面就把苹果官方提供的isa的走位图拿出来做一下解释
- 虚线代表了
isa
的走位。实例对象->类->元类->根元类->根根元类(根元类本身)。- 实线代表了继承关系。这里值得注意的就是根元类的父类是
NSObject
。
继承关系
类的结构体的补充
struct objc_class {
//Class isa
Class superclass;
...省略部分...
}
说明类的第二段内存是指向父类。
类的继承关系
- 定义继承关系
XDTeacher
-> XDPerson
-> NSObject
- 验证继承关系
XDTeacher
的父类验证
(lldb) x/4gx XDTeacher.class
0x1000011f8: 0x001d8001000011d1 0x00000001000011a8
0x100001208: 0x00000001003a1e50 0x0000000000000000
(lldb) po 0x00000001000011a8
XDPerson
XDPerson
的父类验证
(lldb) x/4gx 0x00000001000011a8
0x1000011a8: 0x001d800100001181 0x0000000100aff140
0x1000011b8: 0x00000001003a1e50 0x0000000000000000
(lldb) po 0x0000000100aff140
NSObject
NSObject
的父类验证
(lldb) x/4gx 0x0000000100aff140
0x100aff140: 0x001d800100aff0f1 0x0000000000000000
0x100aff150: 0x000000010105b980 0x0000000100000003
(lldb) po 0x0000000000000000
<nil>
验证了类的继承关系是
XDTeacher
->XDPerson
->NSObject
->nil
。
元类的继承关系
- 类的继承链上的内存地址
(lldb) x/4xg XDTeacher.class
0x1000011f8: 0x001d8001000011d1 0x00000001000011a8
0x100001208: 0x00000001003a1e50 0x0000000000000000
(lldb) po 0x00000001000011a8
XDPerson
(lldb) x/4xg 0x00000001000011a8
0x1000011a8: 0x001d800100001181 0x0000000100aff140
0x1000011b8: 0x00000001003a1e50 0x0000000000000000
(lldb) po 0x0000000100aff140
NSObject
我们做好准备条件把类的父类的内存地址全部先获取。
- 元类的内存地址
XDTeacher的元类
内存地址和元类的父类的内存地址
(lldb) p/x 0x001d8001000011d1 & 0x0000000ffffffff8
(long) $3 = 0x00000001000011d0
(lldb) x/4xg 0x00000001000011d0
0x1000011d0: 0x001d800100aff0f1 0x0000000100001180
0x1000011e0: 0x00000001018002a0 0x0000000300000003
我们获取到了
XDTeacher元类
的父类的内存地址0x0000000100001180
XDPerson的元类
内存地址
(lldb) x/4xg 0x00000001000011a8
0x1000011a8: 0x001d800100001181 0x0000000100aff140
0x1000011b8: 0x00000001003a1e50 0x0000000000000000
(lldb) p/x 0x001d800100001181 & 0x0000000ffffffff8
(long) $4 = 0x0000000100001180
XDPerson的元类
的内存地址是0x0000000100001180
,同XDTeacher元类
的父类的内存地址时相同的。
- 这一步就可以验证了
XDTeacher元类
继承自XDPerson的元类
。
- 全部验证
//查看XDPerson的元类的内存信息
(lldb) x/4xg 0x0000000100001180
0x100001180: 0x001d800100aff0f1 0x0000000100aff0f0 -->XDPerson的元类的父类内存地址
0x100001190: 0x0000000101d09e50 0x0000000300000003
//查看NSObjec的内存信息
(lldb) x/4xg 0x0000000100aff140
0x100aff140: 0x001d800100aff0f1 0x0000000000000000
0x100aff150: 0x000000010183a780 0x0000000100000003
//查看NSObjec的元类内存地址
(lldb) p/x 0x001d800100aff0f1 & 0x0000000ffffffff8
(long) $5 = 0x0000000100aff0f0 -->验证XDPerson的元类->NSObjec的元类
//查看根元类的内存信息
(lldb) x/4xg 0x0000000100aff0f0
0x100aff0f0: 0x001d800100aff0f1 0x0000000100aff140
0x100aff100: 0x0000000101d094c0 0x0000000300000007
//查看根元类的父类
(lldb) x/4xg 0x0000000100aff140
0x100aff140: 0x001d800100aff0f1 0x0000000000000000
0x100aff150: 0x000000010183a780 0x0000000100000003
//这里我么可以提前获取一下打印NSObject.class的内存信息
//方便区分根元类NSObject 和 类NSObject 我这里没有打印
//po NSObject.class
//得到根元类的父类NSObject
(lldb) p/x 0x001d800100aff0f1 & 0x0000000ffffffff8
(long) $7 = 0x0000000100aff0f0
(lldb) po $7
NSObject
(lldb) po 0x0000000000000000
<nil>
通过我们一步步的调试我们的元类的继承关系就出来了。
- XDTeacher元类->XDPerson元类->NSObject元类->NSObject->nil。
通过这一步的流程,也验证了isa
走位图,笔者能力有限,探索不足之处可以在评论区指正。
学习之路,砥砺前行
不足之处可以在评论区指出