在學(xué)習(xí)Runtime的時(shí)候翎迁,你可能要脫離原來你所認(rèn)知的區(qū)域,比如:你真的了解類和對(duì)象么净薛?你真的理解實(shí)例方法和類方法么汪榔?你真的以為你看到的就是所有的東西么?網(wǎng)上的那些所謂的實(shí)用技巧你真的理解什么意思么肃拜?磨刀不誤砍柴工痴腌,我們先說一下很重要的幾個(gè)概念。
1. id 以及 Class
- (我姑且認(rèn)為大家印象中:id就是對(duì)象燃领,Class就是類士聪。)
大家對(duì)于id
和Class
其實(shí)并不陌生,我們做一個(gè)實(shí)驗(yàn)猛蔽,創(chuàng)建一個(gè)Person
的類剥悟,然后創(chuàng)建一個(gè)Person
對(duì)象,然后這樣:
Person* person = [[Person alloc] init];
NSLog(@"%p",person);
//
NSLog(@"%p",[person class]);
NSLog(@"%p",[Person class]);
//
NSLog(@"%p",object_getClass(person));
NSLog(@"%p",object_getClass([person class]));
打印結(jié)果:
RuntimeSkill[2048:247155] 0x60000000ed30
RuntimeSkill[2048:247155] 0x10702c6d0
RuntimeSkill[2048:247155] 0x10702c6d0
RuntimeSkill[2048:247155] 0x10702c6d0
RuntimeSkill[2048:247155] 0x10702c6a8
我擦嘞,就問你懵逼了沒有懦胞?
從結(jié)構(gòu)看功能
寫一個(gè)Class
去看系統(tǒng)的API:
typedef struct objc_class *Class;
同時(shí)你會(huì)發(fā)現(xiàn)有一個(gè)這個(gè)東西typedef struct objc_object *id;
發(fā)現(xiàn)id
是一個(gè)結(jié)構(gòu)體替久,并且里面只有一個(gè)Class
類型的指針isa:
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
再看Class
其實(shí)也是系統(tǒng)定義的一個(gè)結(jié)構(gòu)體,只不過結(jié)構(gòu)復(fù)雜很多:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; //父類
const char *name OBJC2_UNAVAILABLE; //類名
long version OBJC2_UNAVAILABLE; //版本信息
long info OBJC2_UNAVAILABLE; //類信息
long instance_size OBJC2_UNAVAILABLE; //實(shí)例變量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; //成員變量鏈表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; //方法鏈表
struct objc_cache *cache OBJC2_UNAVAILABLE; //方法緩存(大幅提高方法調(diào)用效率)
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; //協(xié)議鏈表
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
通過對(duì)比我們可以確認(rèn)幾點(diǎn):
- 在Objective-C中躏尉,所有的類其實(shí)也是一個(gè)對(duì)象蚯根,我們可以認(rèn)為
id
中的isa指針指向的是一個(gè)類對(duì)象,并且在Class
結(jié)構(gòu)體中的isa指針指向元類(后面做解釋)胀糜。 - 在Runtime中颅拦,以
obj_
開頭的方法大多是針對(duì)id
類型的對(duì)象進(jìn)行操作,而以class_
開頭方法主要是針對(duì)Class
類型的類對(duì)象進(jìn)行操作教藻。 - Runtime的以
class_
開頭的方法是針對(duì)與Class
結(jié)構(gòu)體的各個(gè)元素進(jìn)行操作的距帅。 - 結(jié)構(gòu)體元素的作用詳見注釋,不再多余贅述括堤。
元類(Meta Class)
在objc_object
結(jié)構(gòu)體中碌秸,有一個(gè)objc_class
類型的指針,奇怪的是悄窃,在objc_class
結(jié)構(gòu)體中又有一個(gè)objc_class
類型的指針讥电,這也就引入了元類的概念。
首先轧抗,元類是類對(duì)象的類恩敌,同樣也就是一個(gè)對(duì)象,它的結(jié)構(gòu)也是objc_class
結(jié)構(gòu)横媚,有人現(xiàn)在會(huì)說了纠炮,你這不是扯淡嗎?這么說那元類的isa
指針有指向哪里灯蝴?我讀書少恢口,你不要騙我!G钤辍弧蝇!
其實(shí)這就涉及了一種特殊的機(jī)制,為了不讓這種結(jié)構(gòu)無限延伸下去折砸,Objective-C讓所有的元類的isa
指針指向基類的元類,以此作為它們的所屬類沙峻。即睦授,任何NSObject繼承體系下的元類都使用NSObject的元類作為自己的所屬類,而基類的元類的isa
指針是指向它自己摔寨,并且基類的元類的父類是基類去枷。這樣就形成了一個(gè)完美的閉環(huán)。太他媽有想法了:
這樣我們就可以解釋剛開始的打印結(jié)果了删顶,第一個(gè)地址
0x60000000ed30
為創(chuàng)建的Person
對(duì)象竖螃,第二,第三逗余,第四個(gè)地址0x10702c6d0
為Person
類對(duì)象的地址特咆,最后一個(gè)0x10702c6a8
為Person
類的元類的地址。(注意:元類的調(diào)用只能用object_getClass ()
或者objc_getClass ()
獲得录粱,使用類對(duì)象調(diào)用class
方法是無法獲取到元類的腻格,它只是返回當(dāng)前類對(duì)象而已。)
實(shí)例方法和類方法
下面我們?cè)诜椒ㄕ{(diào)用方面來研究一下元類存在的必然性啥繁,那就要說到實(shí)例方法和類方法發(fā)送消息的機(jī)制菜职,在Class
的結(jié)構(gòu)體中有一個(gè)元素methodLists
,當(dāng)我們調(diào)用一個(gè)方法時(shí)旗闽,系統(tǒng)會(huì)在這個(gè)列表中進(jìn)行查找(這里不考慮cache
)酬核,同樣元類也有一個(gè)methodLists
,所以:
- 當(dāng)給對(duì)象發(fā)送消息時(shí)(調(diào)用實(shí)例方法)适室,系統(tǒng)會(huì)在當(dāng)前對(duì)象對(duì)應(yīng)的類對(duì)象的
methodLists
中進(jìn)行查找嫡意。 - 當(dāng)給類發(fā)送消息時(shí)(調(diào)用類方法),系統(tǒng)會(huì)在當(dāng)前類的元類的
methodLists
中進(jìn)行查找亭病。
也就是說類對(duì)象存儲(chǔ)著一個(gè)類的所有實(shí)例方法鹅很,元類存儲(chǔ)著一個(gè)類的所有類方法。同時(shí)每個(gè)類都會(huì)有一個(gè)單獨(dú)的元類罪帖,因?yàn)槊總€(gè)類的類方法基本不可能完全相同促煮。看到這有人睡說整袁,元類中還有很多的元素菠齿,比如成員變量的鏈表,那類變量怎么說坐昙?目前我沒有找到關(guān)于類變量的信息绳匀。
2. SEL、Method炸客、IMP
關(guān)于SEL
相信大家都很熟悉疾棵,但是對(duì)于Method
和IMP
就相對(duì)陌生了,下面我們?cè)斀膺@幾個(gè)關(guān)于方法的數(shù)據(jù)類型痹仙。
SEL
SEL
是系統(tǒng)在編譯過程中是尔,會(huì)根據(jù)方法的名字以及參數(shù)序列生成一個(gè)用來區(qū)分這個(gè)方法的唯一ID編號(hào),這個(gè) ID 就是 SEL
類型的开仰。我們需要注意的是拟枚,只要方法的名字和參數(shù)序列完全相同,那么它們的 ID編號(hào)就是相同的薪铜。
獲取SEL的幾種方法:
SEL aSel = @selector(didReceiveMemoryWarning);
SEL a_sel = NSSelectorFromString(@"didReceiveMemoryWarning");
SEL a_Sel = sel_registerName("didReceiveMemoryWarning");
NSLog(@"%p___%p___%p",aSel,a_sel,a_Sel);
打印結(jié)果:
RuntimeSkill[1741:214328] 0x10957b985___0x10957b985___0x10957b985
Method
Method
從字面上一看就是方法的意思。Method
其實(shí)就是 objc_method
的結(jié)構(gòu)體指針恩溅,結(jié)構(gòu)如下:
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE; //方法名
char *method_types OBJC2_UNAVAILABLE; //參數(shù)類型以及返回值類型編碼
IMP method_imp OBJC2_UNAVAILABLE; //方法實(shí)現(xiàn)指針
}
獲取Method
的方法:
// 獲取實(shí)例方法
Method class_getInstanceMethod ( Class cls, SEL name );
// 獲取類方法
Method class_getClassMethod ( Class cls, SEL name );
// 獲取所有方法的數(shù)組
Method * class_copyMethodList ( Class cls, unsigned int *outCount );
IMP
IMP
即Implementation
隔箍,為指向函數(shù)實(shí)現(xiàn)的指針,如果我們能夠獲取到這個(gè)指針脚乡,則可以直接調(diào)用該方法蜒滩,充分證實(shí)了它就是一個(gè)函數(shù)的指針。
獲取IMP
的方法:
//通過Method獲取IMP
IMP method_getImplementation(Method m);
// 返回方法的具體實(shí)現(xiàn)
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );
獲取到IMP
之后可直接調(diào)用方法:
SEL aSel = @selector(didReceiveMemoryWarning);
Method method = class_getInstanceMethod([self class], aSel);
IMP imp = method_getImplementation(method);
((void (*) (id, SEL)) (void *)imp)(self, aSel);
3. Ivar
在Class
結(jié)構(gòu)體中每窖,有一個(gè)ivars
的鏈表結(jié)構(gòu)帮掉,其中存儲(chǔ)著所有變量信息(Ivar
的數(shù)組),每一個(gè)Ivar
指針對(duì)應(yīng)一個(gè)變量元素窒典。同時(shí)通過系統(tǒng)的API蟆炊,我們看到Ivar
也是一個(gè)結(jié)構(gòu)體,`typedef struct objc_ivar *Ivar瀑志,它也是一個(gè)結(jié)構(gòu)題:
struct objc_ivar {
char *ivar_name OBJC2_UNAVAILABLE;
char *ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
對(duì)于
Ivar`的操作有以下方法:
// 獲取類中指定名稱成員變量
Ivar class_getInstanceVariable ( Class cls, const char *name );
// 獲取類變量
Ivar class_getClassVariable ( Class cls, const char *name );
// 獲取整個(gè)成員變量列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
- 需要注意的是:
class_copyIvarList
這個(gè)函數(shù)函數(shù)涩搓,返回全部實(shí)例變量的數(shù)組,數(shù)組中每個(gè)Ivar
指向該成員變量信息的objc_ivar
結(jié)構(gòu)體的指針劈猪。這個(gè)數(shù)組不包含在父類中聲明的變量昧甘。outCount指針返回?cái)?shù)組的大小。我們必須使用free()來釋放這個(gè)數(shù)組战得。 - 關(guān)于類變量的傳說連聽過都沒聽過充边,你要是吹牛逼說你知道,那麻煩您教一下我常侦,必有重謝浇冰,哈哈哈哈哈。
總結(jié)
對(duì)于上面的很多的示例代碼只是提供給大家?guī)椭斫獾牧觯姨嵝涯阋稽c(diǎn):開發(fā)中千萬不要這么寫代碼肘习,不然你升職加薪,迎娶白富美坡倔,走上人生巔峰本來就不可能漂佩,現(xiàn)在可能連溫飽也是個(gè)問題了。這也不是單純的扯淡罪塔,明確概念之后投蝉,我們講Runtime的實(shí)際用法才事半功倍。