一、isa到元类
先定义一个
ApplePerson
类
@interface ApplePerson : NSObject
@end
复制代码
添加断点
x p
打印一下对象指针地址
(lldb) x p
0x100553e50: 69 84 00 00 01 00 00 01 00 00 00 00 00 00 00 00 i...............
0x100553e60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
复制代码
发现地址是
0x100553e50
,在p/x
一下指针地址
(lldb) p/x p
(ApplePerson *) $1 = 0x0000000100553e50
复制代码
发现
0x0000000100553e50
和上面0x100553e50
完全一致,再x/4gx
打印一下isa
地址
(lldb) x/4gx p
0x100553e50: 0x0100000100008469 0x0000000000000000
0x100553e60: 0x0000000000000000 0x0000000000000000
复制代码
得到0x0100000100008469
就是isa
的地址,和arm64
的isa
掩码ISA_MASK
&
一下 得出类的地址
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
复制代码
相与
(lldb) p/x 0x0100000100008469 & 0x0000000ffffffff8
(long) $3 = 0x0000000100008468
复制代码
po
一下变量$3
类的地址
(lldb) po 0x0000000100008468
ApplePerson
复制代码
得出
ApplePerson
,那如果对类地址再进行x/4gx
呢
(lldb) x/4gx 0x0000000100008468
0x100008468: 0x0000000100008440 0x00000001eb780d08
0x100008478: 0x0000000180218e60 0x0000802900000000
复制代码
得到一个和上面类似的地址结构,如果假设
0x0000000100008440
是一个isa
,对它和掩码&
一下
(lldb) po 0x0000000100008440 & 0x0000000ffffffff8
ApplePerson
复制代码
也是一个ApplePerson
那对它再&
一下
(lldb) p/x 0x0000000100008440 & 0x0000000ffffffff8
(long) $7 = 0x0000000100008440
复制代码
还是原来的地址,而
0x0000000100008468
和0x0000000100008440
po
出来都是ApplePerson
,我们猜想,内存是不是只开辟了不止一个类,接下里我们通过代码测试一下
//分析类对象在内存里存在的个数
void appleTestClassNum(void){
Class cls1 = [ApplePerson class];
Class cls2 = [ApplePerson alloc].class;
Class cls3 = object_getClass([ApplePerson alloc]);
Class cls4 = [ApplePerson alloc].class;
NSLog(@"\n- %p \n- %p \n- %p \n- %p",cls1,cls2,cls3,cls4);
}
复制代码
打印输出
- 0x100008468
- 0x100008468
- 0x100008468
- 0x100008468
复制代码
发现他们打印指向的地址都是一样的,都是
0x100008468
和上面的0x0000000100008468
是一致的,是类的地址,而0x0000000100008440
不是类的地址,所以它并不是类
分析:对象的
isa
指向类,而类的isa
又指向这个新的结构
graph LR
对象 .- 对象的isa ====> 类 .- 类的isa ====> 新的结构
接下来,我们点开执行文件
把它拉进烂苹果里面
点开objc_classrefs一列,点开ObjC2 References,可以看到只有两个类
ApplePerson
和AppleEmployee
点击符号表,查询class
可以看到一个_OBJC_CLASS_ApplePerson和一个_OBJC_METACLASS_ApplePerson,METACLASS就是元类,我们并没有创建过METACLASS,所以它是系统生成的
graph LR
对象 .- 对象的isa ====> 类 .- 类的isa ====> 元类
二、isa走位图和继承链
上面找到了元类,那元类前面是什么,接下来我们再跑一次项目,依旧是上面的代码,加打印地方断点
ApplePerson *p = [ApplePerson alloc];
NSLog(@"%@",p);
复制代码
x/4gx
一下拿到指针地址
(lldb) x/4gx p
0x100617ea0: 0x0100000100008299 0x0000000000000000
0x100617eb0: 0x0000000000000000 0x0000000000000000
复制代码
和
isa_mask
&
一下拿到类地址
(lldb) p/x 0x0100000100008299 & 0x0000000ffffffff8
(long) $1 = 0x0000000100008298
复制代码
po
打印一下
(lldb) po 0x0000000100008298
ApplePerson
复制代码
对类地址进行
x/4gx
(lldb) x/4gx 0x0000000100008298
0x100008298: 0x0000000100008270 0x00000001eb780d08
0x1000082a8: 0x0000000180218e60 0x0000802900000000
复制代码
继续和
isa_mask
&
一下拿到元类地址
(lldb) p/x 0x0000000100008270 & 0x0000000ffffffff8
(long) $3 = 0x0000000100008270
复制代码
再
po
打印一下
(lldb) po 0x0000000100008270
ApplePerson
复制代码
如果我们继续对元类进行
x/4gx
(lldb) x/4gx 0x0000000100008270
0x100008270: 0x00000001eb780ce0 0x00000001eb780ce0
0x100008280: 0x00010001050376f0 0x0002e03500000000
复制代码
继续和
isa_mask
&
一下
(lldb) p/x 0x00000001eb780ce0 & 0x0000000ffffffff8
(long) $5 = 0x00000001eb780ce0
(lldb)
复制代码
得到
$5
地址0x00000001eb780ce0
和上面的元类地址一模一样
此时,我们po
打印一下$5
(lldb) po 0x00000001eb780ce0
NSObject
(lldb)
复制代码
是
NSObject
,得到了根元类
graph LR
对象 .- 对象的isa ===> 类 .- 类的isa ===> 元类.- 元类的isa===> 根元类
简化一下
graph LR
对象的isa --> 类的isa --> 元类的isa--> 根元类
此时,我们再打印一下跟类
NSObject
类的地址
(lldb) p/x NSObject.class
(Class) $7 = 0x00000001eb780d08 NSObject
(lldb)
复制代码
显然,
$7 = 0x00000001eb780d08
和 上面的0x00000001eb780ce0
并不一致,我们对它再一次x/4gx
(lldb) x/4gx 0x00000001eb780d08
0x1eb780d08: 0x00000001eb780ce0 0x0000000000000000
0x1eb780d18: 0x0001000100612180 0x0001801000000000
复制代码
再拿到isa的地址再x/4gx
(lldb) x/4gx 0x00000001eb780ce0
0x1eb780ce0: 0x00000001eb780ce0 0x00000001eb780d08
0x1eb780cf0: 0x0001000100704080 0x0002e03400000000
复制代码
得到
0x00000001eb780ce0
和上面一致,再和isa_mask
&
一下
(lldb) p/x 0x00000001eb780ce0 & 0x0000000ffffffff8
(long) $8 = 0x00000001eb780ce0
复制代码
依然,还是
0x00000001eb780ce0
graph LR
根类 .- 根类的isa ===> 根元类.-> 根元类
所以,我们推测如下的类结构图:
graph LR
根类对象 ..-> 根类 ....-> 根元类
父类对象 ..-> 父类 ..-> 父类的元类
父类的元类..-> 根元类
子类对象 ..-> 子类 ..-> 子类的元类
子类的元类..-> 根元类
根元类.-> 根元类
接下来,我们通过代码验证元类之间的关系
void appleTestNSObject(void){
// 初始化NSObject根类实例对象
NSObject *obj = [NSObject alloc];
// 拿到NSObject根类
Class obj_class = object_getClass(obj);
// 拿到NSObject的根元类
Class meta_obj_class = object_getClass(obj_class);
// 拿到NSObject的根根元类
Class root_meta_obj_class = object_getClass(meta_obj_class);
// 拿到NSObject的根根根元类
Class root_root_meta_obj_class = object_getClass(root_meta_obj_class);
NSLog(@"\n %p 根类实例对象\n %p 根类\n %p 根元类\n %p 根根元类\n %p 根根根元类\n",obj,obj_class,meta_obj_class,root_meta_obj_class,root_root_meta_obj_class);
}
复制代码
打印一下
0x10512b040 根类实例对象
0x1eb780d08 根类
0x1eb780ce0 根元类
0x1eb780ce0 根根元类
0x1eb780ce0 根根根元类
复制代码
可以发现NSObject的根元类以及往上走都是同一地址,指向了根元类自己
我们再测试一下子类对象AppleEmployee
的元类指向
// AppleEmployee的元类
Class AppleEmployee_MetaClass = object_getClass(AppleEmployee.class);
// AppleEmployee父类的元类
Class AppleEmployee_SuperMetaClass = class_getSuperclass(AppleEmployee_MetaClass);
//AppleEmployee根元类
Class AppleEmployee_RootMetaClass = class_getSuperclass(AppleEmployee_SuperMetaClass);
NSLog(@"子类的元类: %@ - 子类元类的地址%p",AppleEmployee_MetaClass,AppleEmployee_MetaClass);
NSLog(@"父类的元类: %@ - 父类元类的地址%p",AppleEmployee_SuperMetaClass,AppleEmployee_SuperMetaClass);
NSLog(@"子类的根元类: %@ - 子类根元类的地址%p",AppleEmployee_RootMetaClass,AppleEmployee_RootMetaClass);
复制代码
打印得出如下结果:
子类的元类: AppleEmployee - 子类元类的地址0x1000082c0
父类的元类: ApplePerson - 父类元类的地址0x100008270
子类的根元类: NSObject - 子类根元类的地址0x1eb780ce0
复制代码
我们可以看到,子类的元类指向父类的元类,父类的元类指向了根元类
所以,我们得出元类的继承链:
graph BT
子类的元类 --> 父类的元类 --> 根元类
而子类的继承链
//AppleEmployee的父类
Class AppleEmployee_SuperClass = class_getSuperclass(AppleEmployee.class);
//AppleEmployee的根类
Class AppleEmployee_rootClass = class_getSuperclass(AppleEmployee_SuperClass);
//AppleEmployee的根根类
Class AppleEmployee_rootrootClass = class_getSuperclass(AppleEmployee_rootClass);
NSLog(@"\nAppleEmployee的父类 - %@\nAppleEmployee的根类 - %@\nAppleEmployee的根根类 - %@",AppleEmployee_SuperClass,AppleEmployee_rootClass,AppleEmployee_rootrootClass);
复制代码
打印一下
AppleEmployee的父类 - ApplePerson
AppleEmployee的根类 - NSObject
AppleEmployee的根根类 - (null)
复制代码
所以,得出子类的继承链
graph BT
子类 --> 父类 --> 根类 --> nil
我们再查找根元类的父类
// 根元类的父类
Class rootMetaClass__superClass = class_getSuperclass(root_meta_obj_class);
NSLog(@"%@ - %p", rootMetaClass__superClass,rootMetaClass__superClass);
复制代码
打印
NSObject - 0x1eb780d08
复制代码
可以看到,根元类的父类是根类
graph BT
根元类 --> 根类
最终我们得出isa和继续的类结构图【
——>
是继承链,- ->
是isa指向链】
graph LR
根类对象 .-> 根类 .-> 根元类
父类对象 .-> 父类 .-> 父类的元类
父类的元类.-> 根元类
子类对象 .-> 子类 .-> 子类的元类
子类的元类.-> 根元类
根元类.-> 根元类
子类 --> 父类 --> 根类 --> nil
子类的元类 --> 父类的元类 --> 根元类 --> 根类
和苹果官网的图是一致的
三、源码分析类的结构
我们知道类的对象有isa,有成员变量,那类的结构呢
通过源码,我们可以知道类是一个
objc_class
类型的结构体指针
查看
objc_class
的底层源码,可以看到objc_class
是继承于objc_object
,类里面的主要东西
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
复制代码
superclass
我们知道是父类,那cache
和bits
是什么东西,可以看到注释写class_rw_t
,源码拉到下面
class_rw_t *data() const {
return bits.data();
}
复制代码
class_rw_t
返回bits.data
,点进class_rw_t
可以看到有方法列表和属性列表
struct class_rw_t {
...
const method_array_t methods() const {
...
}
const property_array_t properties() const {
...
}
}
复制代码
四、指针和内存平移
现在我们知道一个类,需要获取它的
bits
,拿到类里面的数据,类结构里面的数据也是连续的很像数组,我们知道数组是存取相同数据类型的一种结构,且在内存段里面数据是连续的,而且每个数据的大小是一样的,我们试着用内存去获取数组的数据
先定义一个数组,用指针指向这个数组
int intArr[4] = {1,2,3,4};
int *p = intArr;
TYLog(@"%p",&intArr);
TYLog(@"%p - %p - %p",&intArr[0],&intArr[1],&intArr[2]);
TYLog(@"%p - %p - %p",p+0,p+1,p+2);
复制代码
进行打印
TY打印: 0x16fdff350
TY打印: 0x16fdff350 - 0x16fdff354 - 0x16fdff358
TY打印: 0x16fdff350 - 0x16fdff354 - 0x16fdff358
复制代码
可以看到数组的地址第
1
个元素的地址也是数组的首地址,对数组指针平移1个int类型单位会指向下一个数组元素,接下来用循环打印验证一下
for (int i = 0; i < 4; i++) {
int value = *(p + i);
TYLog(@"%d",value);
}
复制代码
打印结果
TY打印: 1
TY打印: 2
TY打印: 3
TY打印: 4
复制代码
五、类的结构内存计算
我们知道类的地址就是类内存结构的首地址,遵循上面的思路,那我们是否能够平移特定大小去获取类结构里面的其他东西
跑下代码
打印一下类的内存结构
(lldb) x/4gx ApplePerson.class
0x100008468: 0x0000000100008440 0x00000001f7d48d08
0x100008478: 0x000000018c7e0e60 0x0000802900000000
(lldb)
复制代码
看回类的结构
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
…
}
复制代码
类的第一个地址是ISA,这个我们之前已经验证过了,第二个是superclass,我们测试一下
(lldb) po 0x00000001f7d48d08
NSObject
复制代码
NSObject
的确实父类
@interface ApplePerson : NSObject
复制代码
要拿到
class_data_bits_t
的地址需要类的首地址 +isa
长度 +superclass
长度 +cache
长度,isa
是8
字节,superclass
是结构体指针,也是8
字节,cache_t
是一个结构体,里面的静态变量和方法我们不看,成员变量是下面两个
struct cache_t {
…
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
…
}
复制代码
uintptr_t
是8
位,mask_t
查看一下
#if __LP64__
typedef uint32_t mask_t;
复制代码
uint32_t
是位,根据类的内存结构8
字节对齐,所以cache_t
是16
字节,这样我们可以得出类的地址平移8
+8
+16
=32
=0x20
可以得到bits
六、lldb分析类的结构和类的bits数据分析
再次打印类的内存结构
(lldb) x/6gx ApplePerson.class
0x1000083d0: 0x00000001000083a8 0x0000000100379140
0x1000083e0: 0x000000010036d0e0 0x0000802800000000
0x1000083f0: 0x0000000100992be4 0x0000000100008420
复制代码
0x1000083d0
偏移32
位
(lldb) p/x 0x1000083d0 + 0x20
(long) $1 = 0x00000001000083f0
复制代码
和上面的
0x1000083f0
是一致的,由于要验证的bits
是class_data_bits_t
类型,所以强转
(lldb) p (class_data_bits_t *)0x00000001000083f0
(class_data_bits_t *) $2 = 0x00000001000083f0
复制代码
这时候会拿到
bits
,通过源码的class_rw_t
class_rw_t *data() const {
return bits.data();
}
复制代码
发现需要的是
bits
里面的数据bits.data
返回*data()
(lldb) p $2->data()
(class_rw_t *) $3 = 0x0000000100992be0
复制代码
返回的
$3
就是class_rw_t *
类型,和源码class_rw_t *data()
返回值类型一致,打印一下$3
(lldb) p *$3
(class_rw_t) $4 = {
flags = 2148007936
witness = 0
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000200
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
复制代码
上面还没看到我们想要的数据,我们查询一下
struct class_rw_t
const method_array_t methods() const {
...
}
const property_array_t properties() const {
...
}
复制代码
有个
methods()
和properties()
两个结构体方法,获取一下
lldb) p $3.properties()
(const property_array_t) $5 = {
list_array_tt<property_t, property_list_t, RawPtr> = {
= {
list = {
ptr = 0x0000000100008138
}
arrayAndFlag = 4295000376
}
}
}
Fix-it applied, fixed expression was:
$3->properties()
复制代码
里面有个
list
(lldb) p $5.list
(const RawPtr<property_list_t>) $6 = {
ptr = 0x0000000100008138
}
复制代码
出现一个
ptr
(lldb) p $6.ptr
(property_list_t *const) $7 = 0x0000000100008138
复制代码
直接获取
(lldb) p *$7
(property_list_t) $8 = {
entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)
}
复制代码
返回类型是
property_list_t
,查看一下
struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};
复制代码
查看
entsize_list_tt
,发现get(number)会返回数据
struct entsize_list_tt {
…
Element& get(uint32_t i) const {
ASSERT(i < count);
return getOrEnd(i);
}
…
}
复制代码
再查看
property_t
struct property_t {
const char *name;
const char *attributes;
};
复制代码
get(number)方法获取一下
(lldb) p $8.get(0)
(property_t) $9 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb) p $8.get(1)
(property_t) $10 = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
p $8.get(2)
Assertion failed: (i < count), function get, file /Users/…/runtime/objc-runtime-new.h, line 624.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
复制代码
可以看到类的属性参数一致,但成员变量subject是没有,打印类的方法
(lldb) p $3.methods()
(const method_array_t) $4 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
= {
list = {
ptr = 0x0000000100003d70
}
arrayAndFlag = 4294983024
}
}
}
Fix-it applied, fixed expression was:
$3->methods()
复制代码
拿到
list
(lldb) p $4.list
(const method_list_t_authed_ptr<method_list_t>) $5 = {
ptr = 0x0000000100003d70
}
复制代码
拿到ptr
(lldb) p $5.ptr
(method_list_t *const) $6 = 0x0000000100003d70
复制代码
获取方法列表
(lldb) p *$6
(method_list_t) $7 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 2147483660, count = 5)
}
复制代码
count = 5
,有5
个方法,methods
方法是method_array_t
类型
const method_array_t methods() const {
…
}
复制代码
查看
method_array_t
,是个list_array_tt
结构
class method_array_t :
public list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>
{
...
}
复制代码
打印
(lldb) p $7.get(0).name()
(SEL) $8 = "sayHenXi"
(lldb) p $7.get(1).name()
(SEL) $9 = "name"
(lldb) p $7.get(2).name()
(SEL) $10 = "setName:"
(lldb) p $7.get(3).name()
(SEL) $11 = "nickName"
(lldb) p $7.get(4).name()
(SEL) $12 = "setNickName:"
(lldb) p $7.get(5).name()
Assertion failed: (i < count), function get, file /Users/…/runtime/objc-runtime-new.h, line 624.
复制代码
对比下xcode代码
@interface ApplePerson : NSObject{
NSString *subject;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
- (void)sayHenXi;
+ (void)sayNiuBi;
@end
复制代码
可以看到有
5
个方法,4个是属性的set/get
方法,一个是成员方法sayHenXi
,而类方法sayNiuBi
并没有在类的bits.data
的方法列表里面