面試題引發(fā)的思考:
Q: 簡(jiǎn)述Class原理丝里?
- 通過(guò)
bits & FAST_DATA_MASK
可以得到class_rw_t
結(jié)構(gòu)體曲初; class_rw_t
結(jié)構(gòu)體 中存放 方法列表、屬性列表杯聚、協(xié)議列表 等臼婆,
并包括一個(gè)指向class_ro_t
結(jié)構(gòu)體 的指針ro
;class_ro_t
結(jié)構(gòu)體 中存放 方法列表幌绍、屬性列表颁褂、協(xié)議列表 等故响,
并包括 成員變量列表;包括所有 類的初始內(nèi)容颁独。
Q: 簡(jiǎn)述方法緩存cache_t
原理彩届?
-
iOS采用方法緩存
cache_t
,用散列表(哈希表)緩存曾經(jīng)調(diào)用過(guò)的方法奖唯,可以提高方法的查找速度惨缆。
調(diào)用方法時(shí)優(yōu)先去 緩存 中查找此方法;
緩存 中沒(méi)有再去 類對(duì)象 中查找丰捷;
然后將其加入 緩存 中方便下次調(diào)用坯墨。
1. Class
結(jié)構(gòu)的本質(zhì)
上一章對(duì)isa
結(jié)構(gòu)的本質(zhì)做了探究,下面探究Class
的內(nèi)部結(jié)構(gòu)病往。
由iOS底層原理 - OC對(duì)象的本質(zhì)(二)可知:
- class對(duì)象和meta-class對(duì)象結(jié)構(gòu)相似捣染,meta-class對(duì)象是特殊的class對(duì)象;
- 關(guān)于存儲(chǔ)方法:
class對(duì)象存儲(chǔ)對(duì)象方法停巷,meta-class對(duì)象存儲(chǔ)類方法耍攘。
(1) Class
的結(jié)構(gòu)
由OC源碼可以找出Class
的結(jié)構(gòu):
由Class
的結(jié)構(gòu)可知:
- 通過(guò)
bits & FAST_DATA_MASK
可以得到class_rw_t
結(jié)構(gòu)體;class_rw_t
結(jié)構(gòu)體中存放方法列表畔勤、屬性列表蕾各、協(xié)議列表等,并包括一個(gè)指向class_ro_t
結(jié)構(gòu)體的指針ro
庆揪;(rw
即readwrite
可讀可寫(xiě))class_ro_t
結(jié)構(gòu)體中存放類的初始內(nèi)容式曲,包括方法列表、屬性列表缸榛、協(xié)議列表等吝羞,并包括成員變量列表。(ro
即readonly
只讀)
(2) class_rw_t
結(jié)構(gòu)
class_rw_t
結(jié)構(gòu)體中存放方法列表(method_array_t
類型)内颗、屬性列表 (property_array_t
類型)钧排、協(xié)議列表(protocol_array_t
類型),接下來(lái)分析三者的結(jié)構(gòu):
通過(guò)以上源碼可知:
method_array_t
、property_array_t
负懦、protocol_array_t
結(jié)構(gòu)相同筒捺;
下面只對(duì)method_array_t
的內(nèi)部結(jié)構(gòu)進(jìn)行分析,property_array_t
和protocol_array_t
的可以類推纸厉。
由method_array_t
內(nèi)部結(jié)構(gòu)可知:
method_array_t
是一個(gè)二維數(shù)組,每個(gè)元素是method_list_t
五嫂;
method_list_t
是一維數(shù)組颗品,每個(gè)元素是method_t
肯尺;
class_rw_t
里面的methods
、properties
躯枢、protocols
是二維數(shù)組则吟,是可讀可寫(xiě)的,包含了類的初始內(nèi)容锄蹂、分類的內(nèi)容氓仲。
(3) class_ro_t
結(jié)構(gòu)
class_ro_t
結(jié)構(gòu)體中存放方法列表(method_list_t
類型)、屬性列表 (property_list_t
類型)得糜、協(xié)議列表(protocol_list_t
類型)敬扛,所以method_list_t
的內(nèi)部結(jié)構(gòu)如下:
由method_list_t
內(nèi)部結(jié)構(gòu)可知:
method_list_t
是一維數(shù)組,每個(gè)元素是method_t
朝抖;
class_ro_t
里面的baseMethodList
啥箭、baseProtocols
、ivars
治宣、baseProperties
是一維數(shù)組急侥,是只讀的,包含了類的初始內(nèi)容侮邀。
(4) class_rw_t
結(jié)構(gòu)與class_ro_t
結(jié)構(gòu)
由iOS底層原理 - 探尋Category本質(zhì)(一)可知:
class_rw_t
結(jié)構(gòu)體通過(guò)attachList
函數(shù)內(nèi)的memmove
和memcpy
操作將所有分類的對(duì)象方法附加到類對(duì)象的方法列表中坏怪。
通過(guò)源碼進(jìn)入realizeClass
函數(shù),查看class_rw_t
與class_ro_t
兩者之間的關(guān)系:
由realizeClass
函數(shù)可知:
類的初始信息(方法绊茧、屬性铝宵、協(xié)議、成員變量等)存放在
class_ro_t
中按傅;
程序運(yùn)行時(shí)捉超,class_ro_t
中的列表和分類中的列表合并起來(lái)存放在class_rw_t
中。
(5) method_t
結(jié)構(gòu)
由以上分析可知:
class_rw_t
結(jié)構(gòu)體和class_ro_t
結(jié)構(gòu)體的方法列表唯绍,其最小單位都是method_t
結(jié)構(gòu)體拼岳。
通過(guò)源碼進(jìn)入method_t
結(jié)構(gòu)體:
由源碼可知:method_t
結(jié)構(gòu)體包含三個(gè)成員變量,接下對(duì)三者進(jìn)行分析:
1> SEL name;
函數(shù)名
SEL
代表方法/函數(shù)名况芒,即選擇器惜纸,底層結(jié)構(gòu)和char
指針類似;
通過(guò)@selector()
或sel_registerName()
獲得绝骚;
通過(guò)sel_getName()
或NSStringFromSelector()
將SEL
轉(zhuǎn)成字符串耐版;- 不同類中相同名字的方法,所對(duì)應(yīng)的方法選擇器是相同的压汪。
驗(yàn)證如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
SEL sel1 = @selector(init);
SEL sel2 = sel_registerName("init");
NSLog(@"%p %p", sel1, sel2);
const char *str1 = sel_getName(sel1);
NSString *str2 = NSStringFromSelector(sel2);
NSLog(@"%s %@", str1, str2);
}
return 0;
}
// 打印結(jié)果
Demo[1234:567890] 0x7fff7c4a6b8b 0x7fff7c4a6b8b
Demo[1234:567890] init init
打印的地址與方法名都相同粪牲,驗(yàn)證成功。
2> const char *types;
編碼(返回值類型止剖,參數(shù)類型)
再Person
類中寫(xiě)一個(gè)方法:
// TODO: ----------------- Person類 -----------------
@interface Person : NSObject
@end
@implementation Person
- (int)testHeight:(float)height age:(int)age {
return 0;
}
@end
將Person.m
文件轉(zhuǎn)為C++語(yǔ)言腺阳,找到_class_ro_t
結(jié)構(gòu)體落君,因?yàn)樗嬷惖某跏夹畔ⅲ?/p>
由以上源碼可知:
方法名為:testHeight:age:
;
編碼types
為:"i24@0:8f16i20"
亭引;
函數(shù)地址為:_I_Person_testHeight_age_
绎速。
編碼types
為:"i24@0:8f16i20"
,采用了iOS的@encode的指令焙蚓,該指令將具體的類型表示成字符串編碼纹冤。部分編碼如下:
我們通過(guò)該表進(jìn)行分析:
// 函數(shù)默認(rèn)會(huì)帶有self和_cmd兩個(gè)參數(shù)
- (int)testHeight:(float)height age:(int)age;
types - i24@0:8f16i20
types - i 24 @ 0 : 8 f 16 I 20
int id SEL float int
返回值 self _cmd height age
// 分析數(shù)字意義:
24 - 所有參數(shù)占24個(gè)字節(jié)
0 - self是從第0個(gè)字節(jié)開(kāi)始存儲(chǔ)购公,id類型占8個(gè)字節(jié)
8 - _cmd是從第8個(gè)字節(jié)開(kāi)始存儲(chǔ)萌京,SEL類型占8個(gè)字節(jié)
16 - height是從第16個(gè)字節(jié)開(kāi)始存儲(chǔ),float類型占4個(gè)字節(jié)
20 - age是從第20個(gè)字節(jié)開(kāi)始存儲(chǔ)君丁,int類型占4個(gè)字節(jié)
3> IMP imp;
指向函數(shù)的指針(函數(shù)地址)
IMP
的內(nèi)部實(shí)現(xiàn)如下:
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
IMP
代表函數(shù)的具體實(shí)現(xiàn)枫夺,存儲(chǔ)著函數(shù)地址。
2. 方法緩存cache_t
由iOS底層原理 - OC對(duì)象的本質(zhì)(二)可知:
如果調(diào)用對(duì)象方法:
- 首先通過(guò)instance的
isa
指向Class橡庞,
然后在Class對(duì)象中class_rw_t
結(jié)構(gòu)體的methods
數(shù)組中查找方法; - 如果Class對(duì)象中找不到該方法印蔗,
需要通過(guò)superclass
指針找到父類的Class對(duì)象扒最,
然后在父類的Class對(duì)象中class_rw_t
結(jié)構(gòu)體的methods
數(shù)組中查找方法 - 如果父類的Class對(duì)象中找不到該方法,再次通過(guò)
superclass
指針找上一級(jí)的父類华嘹,如此循環(huán)吧趣。
如果一個(gè)方法需要調(diào)用許多次的話,需要循環(huán)遍歷多次以上步驟耙厚;
iOS采用方法緩存cache_t
强挫,用散列表(哈希表)緩存曾經(jīng)調(diào)用過(guò)的方法,可以提高方法的查找速度薛躬。
調(diào)用方法時(shí)優(yōu)先去緩存中查找此方法俯渤,緩存中沒(méi)有再去類對(duì)象中查找,然后將其加入緩存中方便下次調(diào)用型宝。
(1) cache_t
緩存方式
由OC源碼可以找出cache_t
的結(jié)構(gòu):
由cache_t
結(jié)構(gòu)可知:
cache_t
結(jié)構(gòu)體存儲(chǔ)_buckets
八匠、_mask
、_occupied
趴酣;_buckets
散列表梨树,存放方法選擇器充當(dāng)?shù)?code>Key值,以及函數(shù)的內(nèi)存地址IMP
岖寞;_mask
散列表的長(zhǎng)度減1抡四,任何數(shù)通過(guò)與_mask
進(jìn)行按位與運(yùn)算之后獲得的值都會(huì)小于等于_mask
,不會(huì)出現(xiàn)數(shù)組溢出的情況仗谆;_occupied
已經(jīng)緩存的方法數(shù)量床嫌。
cache_t
結(jié)構(gòu)可以歸納如下:
接下來(lái)查看OC內(nèi)部如何處理緩存: