1.类的结构定义
我们在main.m文件中写一段简单的代码:
LGPerson *person = [LGPerson alloc]
Class cls = object_getClass(person);
NSLog(@"preson ==== %@",person);
然后,我们打开终端cd到当前main.m的上层文件夹中,使用clang命令:
clang -rewrite-objc main.m -o main.cpp
这时候会生成一个main.cpp的c++文件,打开main.cpp文件,可以看到这样一行代码:
typedef struct objc_class *Class;
由此可以得出一个结论,Class是一个objc_class的结构体指针,接着,我们需要到objc源码探索一下objc_class的结构。我们在Xcode全局搜索objc_class,会发现有2个定义。第一个是旧版本,在OBJC2中已被废弃:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
在这个版本中,我们可以看到objc_class内的isa是自己定义的属性,并不是从父类继承,而在新版中,objc_class是继承objc_object,我们看到的isa也是来自于父类objc_object,由此可以得出,类也是一种类对象。
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
class_rw_t *data() {
return bits.data();
}
//下面还有一些定义的函数方法,因为太占空间,跟本文无太大关系,所以就省略了
.
.
.
};
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
2.类的属性以及方法存储
我们知道NSObject在底层都会被编译成objc_object的结构体,那么我们在NSObject定义的方法是存在哪里呢?通过类的结构定义,我们可以看到,objc_object内有
1.Class ISA;
2.Class superclass;
3.cache_t cache;
4.class_data_bits_t bits;
看过苹果官方文档的同学应该已经知道,isa是一种关联对象和类的指针,superclass是继承关系,剩下cache_t和class_rw_t,类的属性和方法的存储就只有可能存在这2个地方。我们继续阅读源码,点进去看cache_t发现这是一个结构体,我们继续看cache_t里的属性,发现bucket_t是一个字节为8的结构体指针,mask_t是字节为4的uint32_t类型的值,那么cache_t所占内存为16个字节,我们可以知道类的属性和方法是不可能存在cache_t里,那么,我们可以猜测一下,类的属性和方法存储在bits里。
struct cache_t {
struct bucket_t *_buckets; //8个byte
mask_t _mask;
mask_t _occupied;
};
typedef uint32_t mask_t; //4个byte
1.类的属性的存储
我们先创建一个LGPerson类,继承NSObject:
@interface LGPerson : NSObject{
NSString *lastName;
}
@property (nonatomic, copy)NSString *firstName;
-(void)goodStudy;
+(void)daydayUp;
调试断点代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
Class cls = object_getClass(person);
NSLog(@"preson ==== %@",person);
}
return 0;
}
我们直接p/x 打印出当前cls的地址
(lldb) p/x cls
(Class) $2 = 0x0000000100002668 LGPerson
通过上面类的结构知道,cls里有isa,superclass,catch_t以及bit,其中isa和superclass占8个byte,cache_t占16个byte,那么bit需要$2偏移32个字节才能得到。
(lldb) p 0x0000000100002688
(long) $3 = 4294977160
p (class_data_bits_t *)0x0000000100002688
(class_data_bits_t *) $5 = 0x0000000100002688
通过指针偏移我们得到0x0000000100002688,直接打印发现打印不出来,我们通过源码知道0x0000000100002688是class_data_bits_t的指针,于是我们强转一下打印出来得到$5。
class_rw_t *data() {
return bits.data();
}
class_rw_t中存储了类的属性和方法,我们看到class_rw_t是返回bits.data()这一函数所取的指针,所以我们用$5调用一下bits.data方法,直接取值得到class_rw_t。
(lldb) p $5->data()
(class_rw_t *) $6 = 0x000000010181f850
(lldb) p *$6
(class_rw_t) $7 = {
flags = 2148139008
version = 0
ro = 0x0000000100002558
methods = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002490
arrayAndFlag = 4294976656
}
}
}
properties = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x0000000100002540
arrayAndFlag = 4294976832
}
}
}
protocols = {
list_array_tt<unsigned long, protocol_list_t> = {
= {
list = 0x0000000000000000
arrayAndFlag = 0
}
}
}
firstSubclass = nil
nextSiblingClass = NSDate
demangledName = 0x0000000000000000
}
通过打印,我们很轻松找到了一个熟悉的词properties,这是一个property_array_t类型的二维数组,我们一层一层打印,最终在里面找到了firstName,由此也证实了属性存在rw里。
(lldb) p $7.properties
(property_array_t) $8 = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x0000000100002540
arrayAndFlag = 4294976832
}
}
}
(lldb) p $8.list
(property_list_t *) $9 = 0x0000000100002540
(lldb) p *$9
(property_list_t) $10 = {
entsize_list_tt<property_t, property_list_t, 0> = {
entsizeAndFlags = 16
count = 1
first = (name = "firstName", attributes = "T@\"NSString\",C,N,V_firstName")
}
}
或者
(lldb) p $9->first
(property_t) $12 = (name = "firstName", attributes = "T@\"NSString\",C,N,V_firstName")
2.类的成员变量的存储
(lldb) p/x cls
(Class) $0 = 0x0000000100002668 LGPerson
(lldb) p (class_data_bits_t *)0x0000000100002688
(class_data_bits_t *) $1 = 0x0000000100002688
(lldb) p $1->data()
(class_rw_t *) $2 = 0x0000000100f3a9d0
(lldb) p *$2
(class_rw_t) $3 = {
flags = 2148139008
version = 0
ro = 0x0000000100002558
methods = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002490
arrayAndFlag = 4294976656
}
}
}
properties = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x0000000100002540
arrayAndFlag = 4294976832
}
}
}
protocols = {
list_array_tt<unsigned long, protocol_list_t> = {
= {
list = 0x0000000000000000
arrayAndFlag = 0
}
}
}
firstSubclass = nil
nextSiblingClass = NSDate
demangledName = 0x0000000000000000
}
通过runtime我们知道object_getIvars是获取成员变量的API,但是我们没有在rw里看到ivar,于是我们探索一下ro,看看有没有我们想要的ivar。
(lldb) p $3.ro
(const class_ro_t *) $4 = 0x0000000100002558
(lldb) p *$4
(const class_ro_t) $5 = {
flags = 388
instanceStart = 8
instanceSize = 24
reserved = 0
ivarLayout = 0x0000000100001d9e "\x02"
name = 0x0000000100001d95 "LGPerson"
baseMethodList = 0x0000000100002490
baseProtocols = 0x0000000000000000
ivars = 0x00000001000024f8
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000100002540
}
功夫不负有心人,我们看到了我们想要的ivars,继续走下去,我们最终看到了我们想看到的。
(lldb) p $5.ivars
(const ivar_list_t *const) $6 = 0x00000001000024f8
(lldb) p *$6
(const ivar_list_t) $7 = {
entsize_list_tt<ivar_t, ivar_list_t, 0> = {
entsizeAndFlags = 32
count = 2
first = {
offset = 0x00000001000025e0
name = 0x0000000100001e62 "lastName"
type = 0x0000000100001ece "@\"NSString\""
alignment_raw = 3
size = 8
}
}
}
(lldb) p $7.get(1)
(ivar_t) $8 = {
offset = 0x00000001000025e8
name = 0x0000000100001e6b "_firstName"
type = 0x0000000100001ece "@\"NSString\""
alignment_raw = 3
size = 8
}
至此,我们可以得出一个结论,属性存储在rw里,成员变量存储在ro里。
3.对象方法的存储
我们在查找属性的时候,在rw里发现了有methods这样一个数组,很容易就会联想到,方法是存在这里。我们直接接着上面进行lldb调试打印,
(lldb) p $3.methods
(method_array_t) $9 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002490
arrayAndFlag = 4294976656
}
}
}
(lldb) p $9.list
(method_list_t *) $10 = 0x0000000100002490
(lldb) p *$10
(method_list_t) $11 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 4
first = {
name = "goodStudy"
types = 0x0000000100001e76 "v16@0:8"
imp = 0x0000000100001be0 (LGTest`-[LGPerson goodStudy] at LGPerson.m:12)
}
}
}
我们在method_list_t里发现了第一个方法就是我们定义的对象方法,然后我们又看到count=4,于是可以猜想,会不会类方法在剩下的里面呢?于是我们将剩下的方法通通打印出来,
(lldb) p $11.get(0)
(method_t) $12 = {
name = "goodStudy"
types = 0x0000000100001e76 "v16@0:8"
imp = 0x0000000100001be0 (LGTest`-[LGPerson goodStudy] at LGPerson.m:12)
}
(lldb) p $11.get(1)
(method_t) $13 = {
name = ".cxx_destruct"
types = 0x0000000100001e76 "v16@0:8"
imp = 0x0000000100001cb0 (LGTest`-[LGPerson .cxx_destruct] at LGPerson.m:10)
}
(lldb) p $11.get(2)
(method_t) $14 = {
name = "firstName"
types = 0x0000000100001e7e "@16@0:8"
imp = 0x0000000100001c40 (LGTest`-[LGPerson firstName] at LGPerson.h:16)
}
(lldb) p $11.get(3)
(method_t) $15 = {
name = "setFirstName:"
types = 0x0000000100001e86 "v24@0:8@16"
imp = 0x0000000100001c70 (LGTest`-[LGPerson setFirstName:] at LGPerson.h:16)
}
(lldb) p $11.get(4)
Assertion failed: (i < count), function get, file /Users/isoftstone/Desktop/005-源码流程跟踪/runtime/objc-runtime-new.h, line 117.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
通过lldb调试,我们可以看到第一个方法是我们定义的对象方法,第二个方法是系统的c++方法,第三个和第四个分别是firstName的getter/setter方法,打印第五个的时候我们发现,数组越界了,并没有找到我们的类方法。
4.类方法的存储
通过对rw和ro的探索,我们知道类的属性以及对象方法存储在当前类的rw里,类的成员变量存储在ro里,那么类方法呢?通过lldb调试,我们发现rw和ro并没有适合存储类方法的位置,又通过类的结构知道,isa、superclass、catch_t也不可能存储类方法,那么类方法是不是可能存在元类当中呢?于是我们可以通过lldb调试,在元类中探索一下。在ios isa的初始化&指向分析中,我们已经知道了如何获取类的元类,直接进行打印
(lldb) x/4gx cls
0x100002668: 0x001d800100002641 0x0000000100aff140
0x100002678: 0x00000001003a2290 0x0000000000000000
(lldb) p 0x001d800100002641 & 0x00007ffffffffff8
(long) $1 = 4294977088
(lldb) p $1
(long) $1 = 4294977088
(lldb) po $1
LGPerson
我们以及得到元类$1,接下来按照对象的存储的探索方法进行lldb调试
(lldb) p/x $1
(long) $1 = 0x0000000100002640
(lldb) p (class_data_bits_t *)0x0000000100002660
(class_data_bits_t *) $2 = 0x0000000100002660
(lldb) p $2->data()
(class_rw_t *) $3 = 0x0000000100f8c770
(lldb) p *$3
(class_rw_t) $4 = {
flags = 2685075456
version = 7
ro = 0x0000000100002448
methods = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002428
arrayAndFlag = 4294976552
}
}
}
properties = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x0000000000000000
arrayAndFlag = 0
}
}
}
protocols = {
list_array_tt<unsigned long, protocol_list_t> = {
= {
list = 0x0000000000000000
arrayAndFlag = 0
}
}
}
firstSubclass = nil
nextSiblingClass = 0x00007fffa2f48b28
demangledName = 0x0000000100001d95 "LGPerson"
}
(lldb) p $4.methods
(method_array_t) $5 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002428
arrayAndFlag = 4294976552
}
}
}
(lldb) p $5.list
(method_list_t *) $6 = 0x0000000100002428
(lldb) p *$6
(method_list_t) $7 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 1
first = {
name = "daydayUp"
types = 0x0000000100001e76 "v16@0:8"
imp = 0x0000000100001c10 (LGTest`+[LGPerson daydayUp] at LGPerson.m:17)
}
}
}
最终,我们在元类找到了我们定义的类方法,也证实了类方法是存储在元类当中。
网友评论