声明
由于类的机构分析在理解过程中比较困难,所以本片博客借鉴和使用了了 juejin.cn/post/687147… www.jianshu.com/p/05c8ba273… 两位大神的相关内容,如有问题请联系我并对内容进行修改或删除
—————————————————分割线 ————————————
对象 类 元类
对于实例对象,类对象,元类对象,根元类等的关系理解
- 的isa指向其
- 的isa指向其
- 的isa指向
- 的isa指向
- 的superclass指向其
- 的superclass指向
- 的superclass指向
- 的superclass指向
验证上述结论
//定义了一个LGPerson类,继承自NSObject
LGPerson *objc2 = [LGPerson alloc];
//打印objc2的内存地址
(lldb) p/x objc2
(LGPerson *) $14 = 0x00000001007640f0
//查看其地址指向的内存信息
(lldb) x/4gx 0x00000001007640f0
0x1007640f0: 0x001d8001000021ed 0x0000000000000000
0x100764100: 0x0000000000000000 0x0000000000000000
//查看其isa信息
(lldb) p/x 0x001d8001000021ed & 0x00007ffffffffff8ULL
(unsigned long long) $15 = 0x00000001000021e8
(lldb) po 0x00000001000021e8
LGPerson
//这里我们也可以通过直接打印LGPerson类的地址来对比一下上面的LGPerson到底是不是类对象
(lldb) p/x LGPerson.class
(Class) $18 = 0x00000001000021e8 LGPerson
//接下来继续查看类对象指向的内存信息
(lldb) x/4gx 0x00000001000021e8
0x1000021e8: 0x00000001000021c0 0x0000000100333140
0x1000021f8: 0x000000010032d430 0x0000802400000000
//发现其isa中通过po打印也是一个LGPerson类型的,这就是元类
(lldb) p/x 0x00000001000021c0 & 0x00007ffffffffff8ULL
(unsigned long long) $26 = 0x00000001000021c0
(lldb) po 0x00000001000021c0
LGPerson
//继续查看元类的内存信息
(lldb) x/4gx 0x00000001000021c0
0x1000021c0: 0x00000001003330f0 0x00000001003330f0
0x1000021d0: 0x0000000102a21210 0x0004e03500000007
//它的isa打印出来是个NSObject类型,这就是根元类
(lldb) p/x 0x00000001003330f0 & 0x00007ffffffffff8ULL
(unsigned long long) $25 = 0x00000001003330f0
(lldb) po 0x00000001003330f0
NSObject
//继续查看根元类的内存信息
(lldb) x/4gx 0x00000001003330f0
0x1003330f0: 0x00000001003330f0 0x0000000100333140
0x100333100: 0x00000001006447a0 0x0004e03100000007
//查看根元类的isa信息,发现根元类的isa直接指向了自己
(lldb) p/x 0x00000001003330f0 & 0x00007ffffffffff8ULL
(unsigned long long) $25 = 0x00000001003330f0
(lldb) po 0x00000001003330f0
NSObject
//对象的第2个8字节指针指向它的superclass,可以看到objc2的superclass为0x0000000000000000,说明对象是没有父类一说的
(lldb) x/4gx objc2
0x1007640f0: 0x001d8001000021ed 0x0000000000000000
0x100764100: 0x0000000000000000 0x0000000000000000
//先查看一下LGPerson对象的内存信息,其superclass为0x0000000100333140,po其信息为NSObject,它就是根类
(lldb) x/4gx LGPerson.class
0x1000021e8: 0x00000001000021c0 0x0000000100333140
0x1000021f8: 0x000000010032d430 0x0000802400000000
(lldb) po 0x0000000100333140
NSObject
//这里我们也可以通过直接打印NSObject根类的地址来对比一下上面的0x0000000100333140到底是不是NSObject类对象
(lldb) p/x NSObject.class
(Class) $31 = 0x0000000100333140 NSObject
//继续打印根类对象的内存信息,可以看到其superClass为0x0000000000000000
(lldb) x/4gx 0x0000000100333140
0x100333140: 0x00000001003330f0 0x0000000000000000
0x100333150: 0x0000000100764490 0x0001801000000003
//这里查看一下LGPerson元类的内存信息,其superClass为0x00000001003330f0,这就是根元类
(lldb) x/4gx 0x00000001000021c0
0x1000021c0: 0x00000001003330f0 0x00000001003330f0
0x1000021d0: 0x0000000102a21210 0x0004e03500000007
//继续查看根元类的内存信息,发现其supClass为0x0000000100333140,po打印也是NSObject类型,跟上面打印的NSObject.class类对象的地址是一样的,说明根元类的superClass指向根类对象
(lldb) x/4gx 0x00000001003330f0
0x1003330f0: 0x00000001003330f0 0x0000000100333140
0x100333100: 0x00000001006447a0 0x0004e03100000007
(lldb) po 0x0000000100333140
NSObject
类在oc中实际上是以结构体的形式存在的,所以也可以通过调试和变异将main.m转成cpp文件查看相关类,元类,根类等关系的代码 先用clang命令转换
clang -rewrite-objc main.m -o main.cpp
//如果imort了UIKit
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot / Applications/Xcode.app/Contents/Developer/Platforms/ iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.0.sdk main.m
然后查看结构体以及关系
typedef struct objc_object LGPerson;
...
//该方法中确定了LGPerson类、元类和NSObject类、元类之间的关系
static void OBJC_CLASS_SETUP_$_LGPerson(void ) {
OBJC_METACLASS_$_LGPerson.isa = &OBJC_METACLASS_$_NSObject;
OBJC_METACLASS_$_LGPerson.superclass = &OBJC_METACLASS_$_NSObject;
OBJC_METACLASS_$_LGPerson.cache = &_objc_empty_cache;
OBJC_CLASS_$_LGPerson.isa = &OBJC_METACLASS_$_LGPerson;
OBJC_CLASS_$_LGPerson.superclass = &OBJC_CLASS_$_NSObject;
OBJC_CLASS_$_LGPerson.cache = &_objc_empty_cache;
}
类的结构
objc——class 结构体的代码定义如下 相关变量解读:
- 指向父类信息的指针
- 类实例方法,属性和协议的存储
typedef struct objc_class *Class;
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
...
}
struct objc_object {
private:
isa_t isa;
...
}
bits信息获取
结构体指针偏移
对于下面代码的解释 结构体指针的地址就是该结构体成员第一个成员的地址,然后第一个成员age是int类型,所以往后偏移4个字节,则好指向height
//定义一个结构体MyStu
struct MyStu{
int age;
int height;
};
struct MyStu stu1 = {20, 180};//声明一个变量stu1
struct MyStu *stuP = &stu1;//用一个结体指针指向stu1
//打印stuP的地址
(lldb) p/x stuP
(MyStu *) $28 = 0x00007ffeefbff548
//把地址往后偏移4个字节,并且要告诉系统你指向的地址是个什么类型的值
(lldb) p/x (int *)(0x00007ffeefbff548 + 0x4)
(int *) $29 = 0x00007ffeefbff54c
//打印该指针中的值,拿到height
(lldb) p *$29
(int) $30 = 180
bits信息
//定义了一个protocol
@protocol MyProtocol <NSObject>
@optional
- (void)myProtocolMethod;
@end
//定义了一个LGPerson类,并声明了相关属性和方法
@interface LGPerson : NSObject<MyProtocol>
{
NSString *innerName;
}
@property (nonatomic, strong) NSString *outName;
- (void)sayHello;
+ (void)sayGood;
@end
//1.打印LGPerson.class的指针信息
(lldb) p/x LGPerson.class
(Class) $32 = 0x00000001000025f0 LGPerson
//2.偏移32字节,16进制是0x20,并告诉系统它是一个指向class_data_bits_t类型的指针了
(lldb) p/x (class_data_bits_t *)(0x00000001000025f0 + 0x20)
(class_data_bits_t *) $33 = 0x0000000100002610
//3.调用class_data_bits_t中data()方法拿到class_rw_t类型的结构体指针,类的方法、属性、协议全存在它里面
(lldb) p/x $33->data()
(class_rw_t *) $34 = 0x000000010070c270
//4.调用class_rw_t中的methods()方法,拿到一个method_array_t类型的class
(lldb) p/x $33->data()->methods()
(const method_array_t) $35 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002408
arrayAndFlag = 0x0000000100002408
}
}
}
//5.method_array_t里面有一个属性list,猜测方法列表应该都在它里,先打印其类型,发现是个method_list_t的指针
(lldb) p/x $35.list
(method_list_t *const) $36 = 0x0000000100002408
//6.通过*指针看一下它的值是什么,是个method_list_t类型的结构体,这里可以看到总共有4个方法,并且第一个方法为sayHello
(lldb) p/x *$36
(method_list_t) $37 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 0x0000001a
count = 0x00000004
first = {
name = 0x0000000100000f78 "sayHello"
types = 0x0000000100000e1c "v16@0:8"
imp = 0x0000000100000b90 (KCObjc`-[LGPerson sayHello])
}
}
}
//7.method_list_t继承自entsize_list_tt,它里面有个get方法,可以它来获取方法1
(lldb) p/x $37.get(0)
(method_t) $38 = {
name = 0x0000000100000f78 "sayHello"
types = 0x0000000100000e1c "v16@0:8"
imp = 0x0000000100000b90 (KCObjc`-[LGPerson sayHello])
}
//8.method_list_t继承自entsize_list_tt,它里面有个get方法,可以它来获取方法2
(lldb) p/x $37.get(1)
(method_t) $39 = {
name = 0x0000000100000f81 "outName"
types = 0x0000000100000d82 "@16@0:8"
imp = 0x0000000100000bc0 (KCObjc`-[LGPerson outName])
}
//9.method_list_t继承自entsize_list_tt,它里面有个get方法,可以它来获取方法3
(lldb) p/x $37.get(2)
(method_t) $40 = {
name = 0x0000000100000f89 "setOutName:"
types = 0x0000000100000e30 "v24@0:8@16"
imp = 0x0000000100000be0 (KCObjc`-[LGPerson setOutName:])
}
//10.method_list_t继承自entsize_list_tt,它里面有个get方法,可以它来获取方法4
(lldb) p/x $37.get(3)
(method_t) $41 = {
name = 0x00007fff777bcb90 ".cxx_destruct"
types = 0x0000000100000e1c "v16@0:8"
imp = 0x0000000100000c10 (KCObjc`-[LGPerson .cxx_destruct])
}
//11.尝试获取方法5时已报错
(lldb) p/x $37.get(4)
Assertion failed: (i < count), function get, file /Users/dyf/Desktop/可编译objc源码/runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
//步骤1:拿到LGPerson类对象的指针地址
(lldb) p/x LGPerson.class
(Class) $42 = 0x00000001000025f0 LGPerson
//步骤2:直接依次调用相关方法拿到list,并通过*指针获取其值
(lldb) p/x *((class_data_bits_t *)(0x00000001000025f0 + 0x20))->data().methods().list
(method_list_t) $48 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 0x0000001a
count = 0x00000004
first = {
name = 0x0000000100000f78 "sayHello"
types = 0x0000000100000e1c "v16@0:8"
imp = 0x0000000100000b90 (KCObjc`-[LGPerson sayHello])
}
}
}
Fix-it applied, fixed expression was:
*((class_data_bits_t *)(0x00000001000025f0 + 0x20))->data()->methods().list
//步骤3:获取其中方法列表
(lldb) p/x $48.get(1)
(method_t) $49 = {
name = 0x0000000100000f81 "outName"
types = 0x0000000100000d82 "@16@0:8"
imp = 0x0000000100000bc0 (KCObjc`-[LGPerson outName])
}
总结
在调试过程中中虽然进行了如何查看方法和属性列表,但是类方法+ (void)sayGood;和非property声明的属性NSString *innerName;在上面的过程中没有打印出来的,这是因为类方法和非property声明的属性存在于元类中,我们上面分析的LGPerson.class是类对象并非元类。