一、類和元類的創(chuàng)建時機
這里先拋出結(jié)論:類和元類是在編譯期創(chuàng)建的,即在alloc之前,下面我們通過兩種方式來驗證:
1、LLDB打?。?/h5>
- 斷點在
int main()處:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 0x00007ffffffffff8
Person *person = [Person alloc];
}
return 0;
}
- 這個時候還沒走
[Person alloc];,我們來打印一下:
(lldb) p/x Person.class
(Class) $0 = 0x0000000100001100 Person // 類
(lldb) x/4gx 0x0000000100001100
0x100001100: 0x001d8001000010d9 0x0000000100b35140
0x100001110: 0x00000001003d8280 0x0000000000000000
(lldb) p/x 0x001d8001000010d9 & 0x00007ffffffffff8
(long) $1 = 0x00000001000010d8
(lldb) po $1
Person // 元類
(lldb) x/4gx 0x00000001000010d8
0x1000010d8: 0x001d800100b350f1 0x0000000100b350f0
0x1000010e8: 0x0000000101f3faf0 0x0000000200000003
(lldb) p/x 0x001d800100b350f1 & 0x00007ffffffffff8
(long) $2 = 0x0000000100b350f0
(lldb) po $2
NSObject // 根元類
(lldb) x/4gx 0x0000000100b350f0
0x100b350f0: 0x001d800100b350f1 0x0000000100b35140
0x100b35100: 0x0000000101f400c0 0x0000000400000007
(lldb) p/x 0x001d800100b350f1 & 0x00007ffffffffff8
(long) $3 = 0x0000000100b350f0
(lldb) po $3
NSObject // 根根元類
2、Macho工具:
-
command + B進行編譯,然后將可執(zhí)行文件拖入Macho中查看。
(沒圖,我的Macho打不開了)
二、類結(jié)構(gòu)分析
1、objc_class源碼跟蹤
int main()處:int main(int argc, const char * argv[]) {
@autoreleasepool {
// 0x00007ffffffffff8
Person *person = [Person alloc];
}
return 0;
}
[Person alloc];,我們來打印一下:(lldb) p/x Person.class
(Class) $0 = 0x0000000100001100 Person // 類
(lldb) x/4gx 0x0000000100001100
0x100001100: 0x001d8001000010d9 0x0000000100b35140
0x100001110: 0x00000001003d8280 0x0000000000000000
(lldb) p/x 0x001d8001000010d9 & 0x00007ffffffffff8
(long) $1 = 0x00000001000010d8
(lldb) po $1
Person // 元類
(lldb) x/4gx 0x00000001000010d8
0x1000010d8: 0x001d800100b350f1 0x0000000100b350f0
0x1000010e8: 0x0000000101f3faf0 0x0000000200000003
(lldb) p/x 0x001d800100b350f1 & 0x00007ffffffffff8
(long) $2 = 0x0000000100b350f0
(lldb) po $2
NSObject // 根元類
(lldb) x/4gx 0x0000000100b350f0
0x100b350f0: 0x001d800100b350f1 0x0000000100b35140
0x100b35100: 0x0000000101f400c0 0x0000000400000007
(lldb) p/x 0x001d800100b350f1 & 0x00007ffffffffff8
(long) $3 = 0x0000000100b350f0
(lldb) po $3
NSObject // 根根元類
Macho工具:command + B進行編譯,然后將可執(zhí)行文件拖入Macho中查看。(沒圖,我的
Macho打不開了)objc_class源碼跟蹤以下源碼來自objc-runtime-new.h文件中,從源碼我們可以看到類的本質(zhì)是結(jié)構(gòu)體,繼承自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();
}
// 省略代碼
};
再繼續(xù)查看objc_object:
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
兩個說明:
-
OC底層實現(xiàn)是C語言,所以objc_object即是NSObject的表現(xiàn)形式。 -
isa的類型為Class的原因有三點:- 萬物皆對象,
isa是可以由Class來接受的。 - 早期調(diào)用
isa是用來返回類的,后面是通過nonpointer區(qū)分純凈isa和優(yōu)化的isa。 - 源碼:
return (Class)(isa.bits & ISA_MASK),進行了Class類型強轉(zhuǎn)。
- 萬物皆對象,
2、objc_class分析
1)內(nèi)存占用分析:
Class ISA; :// 8字節(jié)
Class superclass; :// 8字節(jié)
cache_t cache; :// 16字節(jié)
前兩個很好理解,我們順著源碼點進去可以看到都是結(jié)構(gòu)體指針,我們看一下cache_t源碼:
struct cache_t {
struct bucket_t *_buckets; // 8字節(jié) 注意這里是結(jié)構(gòu)體指針,指針占8字節(jié)
mask_t _mask; // 4字節(jié) mask_t類型為uint32_t ,int占4字節(jié)
mask_t _occupied; // 4字節(jié) mask_t類型為uint32_t ,int占4字節(jié)
// 省略代碼
};
以上,我們可以得出前三個屬性的內(nèi)存偏移量是8+8+16=32字節(jié),這里32字節(jié)是在10進制下,在16進制下則是20字節(jié)。
2)bits存儲信息探索
在開始探索之前我們看一下Person文件的代碼:
Person.h
@interface Person : NSObject{
NSString *hobby;
}
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, copy) NSString *sex;
- (void)sayHello;
+ (void)sayHappy;
@end
Person.m
@implementation LGPerson
- (void)sayHello{
NSLog(@"Person say : Hello!!!");
}
+ (void)sayHappy{
NSLog(@"Person say : Happy!!!");
}
在NSLog處斷點,通過x/4gx打印地址
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [Person alloc];
Class pClass = object_getClass(person);
NSLog(@"-------");
}
打印結(jié)果:
(lldb) x/4gx pClass
0x100002438: 0x001d800100002411 0x0000000100b37140
0x100002448: 0x00000001003da260 0x0000000000000000
通過以上對源碼的分析,我們可以知道0x001d800100002411是ISA,0x0000000100b37140是superclass,0x00000001003da260是cache,0x0000000000000000是bits。
上文已經(jīng)算出前三個屬性占用內(nèi)存偏移為20字節(jié),為了得到class_data_bits_t的地址,我們需要做一下地址偏移運算:0x100002438 + 0x20 = 0x100002458。
- 首先取到
class_ro_t:
(lldb) x/4gx pClass
0x100002438: 0x001d800100002411 0x0000000100b37140
0x100002448: 0x00000001003da260 0x0000000000000000
(lldb) p (class_data_bits_t *)0x100002458 // 類型轉(zhuǎn)換
(class_data_bits_t *) $1 = 0x0000000100002458
(lldb) p $1->data()
(class_rw_t *) $2 = 0x0000000100f55d30
(lldb) p *$2
(class_rw_t) $3 = {
flags = 2148139008
version = 0
ro = 0x0000000100002388
methods = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002260
arrayAndFlag = 4294976096
}
}
}
properties = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x0000000100002360
arrayAndFlag = 4294976352
}
}
}
protocols = {
list_array_tt<unsigned long, protocol_list_t> = {
= {
list = 0x0000000000000000
arrayAndFlag = 0
}
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
demangledName = 0x0000000000000000
}
(lldb) p $3.ro
(const class_ro_t *) $4 = 0x0000000100002388
(lldb) p *$4
(const class_ro_t) $5 = {
flags = 388
instanceStart = 8
instanceSize = 32
reserved = 0
ivarLayout = 0x0000000100001f81 "\x03"
name = 0x0000000100001f78 "LGPerson"
baseMethodList = 0x0000000100002260
baseProtocols = 0x0000000000000000
ivars = 0x00000001000022f8
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000100002360
}
-
屬性:我們的屬性是存放在
baseProperties中的:
(lldb) p $5.baseProperties
(property_list_t *const) $6 = 0x0000000100002360
(lldb) p *$6
(property_list_t) $7 = {
entsize_list_tt<property_t, property_list_t, 0> = {
entsizeAndFlags = 16
count = 2
first = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
}
}
到這里我們就取到了nickName,但是count為2,這里只看到first,那我們來嘗試取一下另一個屬性:
(lldb) p $7.get(1)
(property_t) $13 = (name = "sex", attributes = "T@\"NSString\",C,N,V_sex")
-
成員變量:存放在
ivars中:
(lldb) p $5.ivars
(const ivar_list_t *const) $8 = 0x00000001000022f8
(lldb) p *$8
(const ivar_list_t) $9 = {
entsize_list_tt<ivar_t, ivar_list_t, 0> = {
entsizeAndFlags = 32
count = 3
first = {
offset = 0x00000001000023f8
name = 0x0000000100001e34 "hobby"
type = 0x0000000100001f9e "@\"NSString\""
alignment_raw = 3
size = 8
}
}
}
同樣取到其他兩個元素:
(lldb) p $8->get(1)
(ivar_t) $20 = {
offset = 0x0000000100002400
name = 0x0000000100001e3a "_nickName"
type = 0x0000000100001f9e "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $8->get(2)
(ivar_t) $21 = {
offset = 0x0000000100002408
name = 0x0000000100001e44 "_sex"
type = 0x0000000100001f9e "@\"NSString\""
alignment_raw = 3
size = 8
}
如上,可以看到生成的成員變量也是存在ivars中的。
-
方法:接下來看一下
baseMethodList里存放的是啥。
(lldb) p $5.baseMethodList
(method_list_t *const) $10 = 0x0000000100002260
(lldb) p *$10
(method_list_t) $11 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 6
first = {
name = "sayHello"
types = 0x0000000100001f83 "v16@0:8"
imp = 0x0000000100001ad0 (LGTest`-[LGPerson sayHello] at LGPerson.m:13)
}
}
}
意料之中我們?nèi)〉搅?code>sayHello方法,但是這里count為什么是6呢?我們來繼續(xù)打?。?/p>
(lldb) p $11.get(1)
(method_t) $23 = {
name = "sex"
types = 0x0000000100001f8b "@16@0:8"
imp = 0x0000000100001ba0 (LGTest`-[LGPerson sex] at LGPerson.h:18)
}
(lldb) p $11.get(2)
(method_t) $24 = {
name = "setSex:"
types = 0x0000000100001f93 "v24@0:8@16"
imp = 0x0000000100001bd0 (LGTest`-[LGPerson setSex:] at LGPerson.h:18)
}
(lldb) p $11.get(3)
(method_t) $25 = {
name = ".cxx_destruct"
types = 0x0000000100001f83 "v16@0:8"
imp = 0x0000000100001c10 (LGTest`-[LGPerson .cxx_destruct] at LGPerson.m:11)
一一取完發(fā)現(xiàn)系統(tǒng)生成的setter/getter方法以及.cxx_destruct析構(gòu)函數(shù)都是存放在baseMethodList中的,,但是并沒有看到類方法+ (void)sayHappy;。那么類方法是存在哪里呢?
在isa探索一文中我們已經(jīng)對類方法的存儲做了分析,類方法是存在元類中的。
這里有兩種方式可以驗證:
1)按照上面的方法繼續(xù)用lldb來打印分析。
(lldb) x/4gx pClass
0x100002438: 0x001d800100002411 0x0000000100b37140
0x100002448: 0x00000001003da260 0x0000000000000000
// 通過isa & mask 取到 pClass的元類
(lldb) p/x 0x001d800100002411 & 0x00007ffffffffff8
(long) $29 = 0x0000000100002410
(lldb) x/4gx $29
0x100002410: 0x001d800100b370f1 0x0000000100b370f0
0x100002420: 0x0000000100f55d90 0x0000000100000003
(lldb) p (class_data_bits_t *)0x100002430
(class_data_bits_t *) $30 = 0x0000000100002430
(lldb) p $30->data()
(class_rw_t *) $32 = 0x0000000100f55cf0
(lldb) p $32->ro
(const class_ro_t *) $35 = 0x0000000100002218
(lldb) p *$35
(const class_ro_t) $36 = {
flags = 389
instanceStart = 40
instanceSize = 40
reserved = 0
ivarLayout = 0x0000000000000000
name = 0x0000000100001f78 "Person"
baseMethodList = 0x00000001000021f8
baseProtocols = 0x0000000000000000
ivars = 0x0000000000000000
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000000000000
}
(lldb) p $35.baseMethodList
(method_list_t *const) $37 = 0x00000001000021f8
Fix-it applied, fixed expression was:
$35->baseMethodList
(lldb) p $35->baseMethodList
(method_list_t *const) $38 = 0x00000001000021f8
(lldb) p *$38
(method_list_t) $39 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 1
first = {
name = "sayHappy"
types = 0x0000000100001f83 "v16@0:8"
imp = 0x0000000100001b00 (Test`+[Person sayHappy] at Person.m:17)
}
}
}
2)利用runtime提供的API來調(diào)用分析。
void testInstanceMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));
Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4);
NSLog(@"%s",__func__);
}
輸出結(jié)果:
2020-03-16 15:44:57.692317+0800 Test[85991:2268904] 0x100002268-0x0-0x0-0x100002200
2020-03-16 15:44:57.692674+0800 Test[85991:2268904] testInstanceMethod_classToMetaclass
sayHello在類中是有值的,在元類中是沒有值的。
sayHappy在類中是沒有值的,在元類中是有值的。
三、總結(jié):
- 類創(chuàng)建時機是在編譯期。
- 類的本質(zhì)是對象(萬物皆對象)。
-
class_rw_t是可以在運行時來拓展類的一些屬性、方法和協(xié)議等內(nèi)容。 -
class_ro_t是在編譯時就已經(jīng)確定了的,存儲的是類的成員變量、屬性、方法和協(xié)議等內(nèi)容。 - 實例方法存在類中。
- 類方法存在元類中。
lldb命令:
-
p/t: 二進制打印 -
p/o: 八進制打印 -
p/x: 十六進制打印 -
p/d: 十進制打印
以上
如有不當(dāng),歡迎指正,感謝。