前言c++
isa
的引出iOS 底层探索篇 —— 内存字节对齐分析这篇文章中,咱们经过lldb
调试的时候,第一个内存段咱们并非直接打印po
出来的,而是po
0x00000001029570d0 & 0x0000000ffffffff8
这样的操做,来打印出来的对象。bash
0x00000001029570d0
这片内存段其实就是isa
。0x0000000ffffffff8
这个值就是ISA_MASK
掩码的值。架构
iOS 底层探索篇 —— 对象的本质这篇文章中,咱们知道了对象的本质就是结构体,经过继承关系找到父类在底层就是objc_objcet
的成员isa
。ide
isa
的初始化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
就会作一些初始化的赋值。
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
。post
# 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
的指向分析XDPerson *person = [XDPerson alloc];
复制代码
1.1 经过控制台调试ui
简单介绍一下
lldb
命令this
x/4gx
objc
打印objc
的4段内存信息。扩展:x/6gx
就是打印6段内存信息。p/t
p/t 打印二进制信息;p/o
打印八进制信息;p/x
打印十六进制信息;
从ISA_BITFIELD
里面的shiftCls
咱们了解到是存储类的指针的。spa
(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
指向了类。
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
走位图,笔者能力有限,探索不足之处能够在评论区指正。