Objective-C 的 Runtime 鑄就了它動(dòng)態(tài)語(yǔ)言的特性,這些深層次的知識(shí)雖然平時(shí)寫(xiě)代碼用的少一些狭郑,但是卻是每個(gè) Objc 程序員需要了解的。
簡(jiǎn)介
因?yàn)镺bjc是一門(mén)動(dòng)態(tài)語(yǔ)言,所以它總是想辦法把一些決定工作從編譯連接推遲到運(yùn)行時(shí)祝迂。也就是說(shuō)只有編譯器是不夠的,還需要一個(gè)運(yùn)行時(shí)系統(tǒng) (runtime system) 來(lái)執(zhí)行編譯后的代碼器净。這就是 Objective-C Runtime 系統(tǒng)存在的意義型雳,它是整個(gè)Objc運(yùn)行框架的一塊基石。
Runtime其實(shí)有兩個(gè)版本:“modern”和 “l(fā)egacy”山害。我們現(xiàn)在用的 Objective-C 2.0 采用的是現(xiàn)行(Modern)版的Runtime系統(tǒng)纠俭,只能運(yùn)行在 iOS 和 OS X 10.5 之后的64位程序中。而OS X較老的32位程序仍采用 Objective-C 1中的(早期)Legacy 版本的 Runtime 系統(tǒng)浪慌。這兩個(gè)版本最大的區(qū)別在于當(dāng)你更改一個(gè)類(lèi)的實(shí)例變量的布局時(shí)冤荆,在早期版本中你需要重新編譯它的子類(lèi),而現(xiàn)行版就不需要眷射。
Runtime基本是用C和匯編寫(xiě)的匙赞,可見(jiàn)蘋(píng)果為了動(dòng)態(tài)系統(tǒng)的高效而作出的努力。你可以在這里下到蘋(píng)果維護(hù)的開(kāi)源代碼妖碉。蘋(píng)果和GNU各自維護(hù)一個(gè)開(kāi)源的runtime版本涌庭,這兩個(gè)版本之間都在努力的保持一致。
與Runtime交互
Objc 從三種不同的層級(jí)上與 Runtime 系統(tǒng)進(jìn)行交互欧宜,分別是通過(guò) Objective-C 源代碼坐榆,通過(guò) Foundation 框架的NSObject類(lèi)定義的方法,通過(guò)對(duì) runtime 函數(shù)的直接調(diào)用冗茸。
Objective-C源代碼
大部分情況下你就只管寫(xiě)你的Objc代碼就行席镀,runtime 系統(tǒng)自動(dòng)在幕后辛勤勞作著。
還記得引言中舉的例子吧夏漱,消息的執(zhí)行會(huì)使用到一些編譯器為實(shí)現(xiàn)動(dòng)態(tài)語(yǔ)言特性而創(chuàng)建的數(shù)據(jù)結(jié)構(gòu)和函數(shù)豪诲,Objc中的類(lèi)、方法和協(xié)議等在 runtime 中都由一些數(shù)據(jù)結(jié)構(gòu)來(lái)定義挂绰,這些內(nèi)容在后面會(huì)講到屎篱。(比如objc_msgSend函數(shù)及其參數(shù)列表中的id和SEL都是啥)
NSObject的方法
Cocoa 中大多數(shù)類(lèi)都繼承于NSObject類(lèi),也就自然繼承了它的方法葵蒂。最特殊的例外是NSProxy交播,它是個(gè)抽象超類(lèi),它實(shí)現(xiàn)了一些消息轉(zhuǎn)發(fā)有關(guān)的方法践付,可以通過(guò)繼承它來(lái)實(shí)現(xiàn)一個(gè)其他類(lèi)的替身類(lèi)或是虛擬出一個(gè)不存在的類(lèi)秦士,說(shuō)白了就是領(lǐng)導(dǎo)把自己展現(xiàn)給大家風(fēng)光無(wú)限,但是把活兒都交給幕后小弟去干永高。
有的NSObject中的方法起到了抽象接口的作用隧土,比如description方法需要你重載它并為你定義的類(lèi)提供描述內(nèi)容提针。NSObject還有些方法能在運(yùn)行時(shí)獲得類(lèi)的信息,并檢查一些特性次洼,比如class返回對(duì)象的類(lèi)关贵;isKindOfClass:和isMemberOfClass:則檢查對(duì)象是否在指定的類(lèi)繼承體系中;respondsToSelector:檢查對(duì)象能否響應(yīng)指定的消息卖毁;conformsToProtocol:檢查對(duì)象是否實(shí)現(xiàn)了指定協(xié)議類(lèi)的方法;methodForSelector:則返回指定方法實(shí)現(xiàn)的地址落萎。
Runtime的函數(shù)
Runtime 系統(tǒng)是一個(gè)由一系列函數(shù)和數(shù)據(jù)結(jié)構(gòu)組成亥啦,具有公共接口的動(dòng)態(tài)共享庫(kù)。頭文件存放于/usr/include/objc
目錄下练链。許多函數(shù)允許你用純C代碼來(lái)重復(fù)實(shí)現(xiàn) Objc 中同樣的功能翔脱。雖然有一些方法構(gòu)成了NSObject
類(lèi)的基礎(chǔ),但是你在寫(xiě) Objc 代碼時(shí)一般不會(huì)直接用到這些函數(shù)的媒鼓,除非是寫(xiě)一些 Objc 與其他語(yǔ)言的橋接或是底層的debug工作届吁。在Objective-C Runtime Reference中有對(duì) Runtime 函數(shù)的詳細(xì)文檔。
Runtime術(shù)語(yǔ)
消息發(fā)送的objc_msgSend:方法吧绿鸣,它的真身是這樣的:
id objc_msgSend ( id self, SEL op, ... );
下面將會(huì)逐漸展開(kāi)介紹一些術(shù)語(yǔ)疚沐,其實(shí)它們都對(duì)應(yīng)著數(shù)據(jù)結(jié)構(gòu)。
SEL
objc_msgSend函數(shù)第二個(gè)參數(shù)類(lèi)型為SEL潮模,它是selector在Objc中的表示類(lèi)型(Swift中是Selector類(lèi))亮蛔。selector是方法選擇器,可以理解為區(qū)分方法的 ID擎厢,而這個(gè) ID 的數(shù)據(jù)結(jié)構(gòu)是SEL:
typedef struct objc_selector *SEL;
其實(shí)它就是個(gè)映射到方法的C字符串究流,你可以用 Objc 編譯器命令@selector()
或者 Runtime 系統(tǒng)的sel_registerName
函數(shù)來(lái)獲得一個(gè)SEL類(lèi)型的方法選擇器。
不同類(lèi)中相同名字的方法所對(duì)應(yīng)的方法選擇器是相同的动遭,即使方法名字相同而變量類(lèi)型不同也會(huì)導(dǎo)致它們具有相同的方法選擇器芬探,于是 Objc 中方法命名有時(shí)會(huì)帶上參數(shù)類(lèi)型(NSNumber一堆抽象工廠方法拿走不謝),Cocoa 中有好多長(zhǎng)長(zhǎng)的方法哦厘惦。
id
objc_msgSend第一個(gè)參數(shù)類(lèi)型為id偷仿,大家對(duì)它都不陌生,它是一個(gè)指向類(lèi)實(shí)例的指針:
typedef struct objc_object *id;
那objc_object又是啥呢:
struct objc_object { Class isa; };
objc_object結(jié)構(gòu)體包含一個(gè)isa指針绵估,根據(jù)isa指針就可以順藤摸瓜找到對(duì)象所屬的類(lèi)炎疆。
Class
之所以說(shuō)isa是指針是因?yàn)镃lass其實(shí)是一個(gè)指向objc_class結(jié)構(gòu)體的指針:
typedef struct objc_class *Class;
而objc_class就是我們摸到的那個(gè)瓜,里面的東西多著呢:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; //isa指針指向Meta Class国裳,因?yàn)镺bjc的類(lèi)的本身也是一個(gè)Object形入,為了處理這個(gè)關(guān)系,r untime就創(chuàng)造了Meta Class缝左,當(dāng)給類(lèi)發(fā)送[NSObject alloc]這樣消息時(shí)亿遂,實(shí)際上是把這個(gè)消息發(fā)給了Class Object
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父類(lèi)
const char *name OBJC2_UNAVAILABLE; // 類(lèi)名
long version OBJC2_UNAVAILABLE; // 類(lèi)的版本信息浓若,默認(rèn)為0
long info OBJC2_UNAVAILABLE; // 類(lèi)信息,供運(yùn)行期使用的一些位標(biāo)識(shí)
long instance_size OBJC2_UNAVAILABLE; // 該類(lèi)的實(shí)例變量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 該類(lèi)的成員變量鏈表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定義的鏈表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法緩存蛇数,對(duì)象接到一個(gè)消息會(huì)根據(jù)isa指針查找消息對(duì)象挪钓,這時(shí)會(huì)在method Lists中遍歷,如果cache了耳舅,常用的方法調(diào)用時(shí)就能夠提高調(diào)用的效率碌上。
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 協(xié)議鏈表
#endif
} OBJC2_UNAVAILABLE;
可以看到運(yùn)行時(shí)一個(gè)類(lèi)還關(guān)聯(lián)了它的超類(lèi)指針,類(lèi)名浦徊,成員變量馏予,方法,緩存盔性,還有附屬的協(xié)議霞丧。
其中objc_ivar_list和objc_method_list分別是成員變量列表和方法列表:
struct objc_ivar_list {
int ivar_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
struct objc_method_list {
struct objc_method_list *obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
}
如果你C語(yǔ)言不是特別好,可以直接理解為objc_ivar_list結(jié)構(gòu)體存儲(chǔ)著objc_ivar數(shù)組列表冕香,而objc_ivar結(jié)構(gòu)體存儲(chǔ)了類(lèi)的單個(gè)成員變量的信息蛹尝;同理objc_method_list結(jié)構(gòu)體存儲(chǔ)著objc_method數(shù)組列表,而objc_method結(jié)構(gòu)體存儲(chǔ)了類(lèi)的某個(gè)方法的信息悉尾。
最后要提到的還有一個(gè)objc_cache突那,顧名思義它是緩存,它在objc_class的作用很重要焕襟,在后面會(huì)講到陨收。
一個(gè) ObjC 類(lèi)同時(shí)也是一個(gè)對(duì)象,為了處理類(lèi)和對(duì)象的關(guān)系鸵赖,runtime 庫(kù)創(chuàng)建了一種叫做元類(lèi) (Meta Class) 的東西务漩。當(dāng)你發(fā)出一個(gè)類(lèi)似[NSObject alloc]的消息時(shí),你事實(shí)上是把這個(gè)消息發(fā)給了一個(gè)類(lèi)對(duì)象 (Class Object) ,這個(gè)類(lèi)對(duì)象必須是一個(gè)元類(lèi)的實(shí)例,而這個(gè)元類(lèi)同時(shí)也是一個(gè)根元類(lèi) (root meta class) 的實(shí)例悦荒。你會(huì)說(shuō) NSObject 的子類(lèi)時(shí),你的類(lèi)就會(huì)指向 NSObject 做為其超類(lèi)居触。但是所有的元類(lèi)最終都指向根元類(lèi)為其超類(lèi)。所有的元類(lèi)的方法列表都有能夠響應(yīng)消息的類(lèi)方法老赤。所以當(dāng) [NSObject alloc] 這條消息發(fā)給類(lèi)對(duì)象的時(shí)候轮洋,objc_msgSend()會(huì)去它的元類(lèi)里面去查找能夠響應(yīng)消息的方法,如果找到了抬旺,然后對(duì)這個(gè)類(lèi)對(duì)象執(zhí)行方法調(diào)用弊予。
圖實(shí)線(xiàn)是 super_class 指針,虛線(xiàn)是isa指針开财。 有趣的是根元類(lèi)的超類(lèi)是NSObject汉柒,而isa指向了自己误褪,而NSObject的超類(lèi)為nil,也就是它沒(méi)有超類(lèi)碾褂。
Method
Method是一種代表類(lèi)中的某個(gè)方法的類(lèi)型兽间。
typedef struct objc_method *Method;
而objc_method在上面的方法列表中提到過(guò),它存儲(chǔ)了方法名正塌,方法類(lèi)型和方法實(shí)現(xiàn):
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
- 方法名類(lèi)型為SEL嘀略,前面提到過(guò)相同名字的方法即使在不同類(lèi)中定義,它們的方法選擇器也相同传货。
- 方法類(lèi)型method_types是個(gè)char指針屎鳍,其實(shí)存儲(chǔ)著方法的參數(shù)類(lèi)型和返回值類(lèi)型。
- method_imp指向了方法的實(shí)現(xiàn)问裕,本質(zhì)上是一個(gè)函數(shù)指針,后面會(huì)詳細(xì)講到孵坚。
Ivar
Ivar是一種代表類(lèi)中實(shí)例變量的類(lèi)型粮宛。
typedef struct objc_ivar *Ivar;
而objc_ivar在上面的成員變量列表中也提到過(guò):
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
}
PS:OBJC2_UNAVAILABLE
之類(lèi)的宏定義是蘋(píng)果在 Objc 中對(duì)系統(tǒng)運(yùn)行版本進(jìn)行約束的黑魔法,有興趣的可以查看源代碼卖宠。
IMP
IMP在objc.h中的定義是:
typedef id (*IMP)(id, SEL, ...);
它就是一個(gè)函數(shù)指針巍杈,這是由編譯器生成的。當(dāng)你發(fā)起一個(gè) ObjC 消息之后扛伍,最終它會(huì)執(zhí)行的那段代碼筷畦,就是由這個(gè)函數(shù)指針指定的。而IMP這個(gè)函數(shù)指針就指向了這個(gè)方法的實(shí)現(xiàn)刺洒。既然得到了執(zhí)行某個(gè)實(shí)例某個(gè)方法的入口鳖宾,我們就可以繞開(kāi)消息傳遞階段,直接執(zhí)行方法逆航,這在后面會(huì)提到鼎文。
你會(huì)發(fā)現(xiàn)IMP
指向的方法與objc_msgSend
函數(shù)類(lèi)型相同,參數(shù)都包含id
和SEL
類(lèi)型因俐。每個(gè)方法名都對(duì)應(yīng)一個(gè)SEL
類(lèi)型的方法選擇器拇惋,而每個(gè)實(shí)例對(duì)象中的SEL
對(duì)應(yīng)的方法實(shí)現(xiàn)肯定是唯一的,通過(guò)一組id
和SEL
參數(shù)就能確定唯一的方法實(shí)現(xiàn)地址抹剩;反之亦然撑帖。
Cache
在runtime.h中Cache的定義如下:
typedef struct objc_cache *Cache
還記得之前objc_class結(jié)構(gòu)體中有一個(gè)struct objc_cache *cache吧,它到底是緩存啥的呢澳眷,先看看objc_cache的實(shí)現(xiàn):
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1] OBJC2_UNAVAILABLE;
};
Cache為方法調(diào)用的性能進(jìn)行優(yōu)化胡嘿,通俗地講,每當(dāng)實(shí)例對(duì)象接收到一個(gè)消息時(shí)境蔼,它不會(huì)直接在isa指向的類(lèi)的方法列表中遍歷查找能夠響應(yīng)消息的方法灶平,因?yàn)檫@樣效率太低了伺通,而是優(yōu)先在Cache中查找。Runtime 系統(tǒng)會(huì)把被調(diào)用的方法存到Cache中(理論上講一個(gè)方法如果被調(diào)用逢享,那么它有可能今后還會(huì)被調(diào)用)罐监,下次查找的時(shí)候效率更高。這根計(jì)算機(jī)組成原理中學(xué)過(guò)的 CPU 繞過(guò)主存先訪問(wèn)Cache的道理挺像瞒爬,而我猜蘋(píng)果為提高Cache命中率應(yīng)該也做了努力吧弓柱。
這篇介紹先到這里 關(guān)于消息發(fā)送的有關(guān)理解請(qǐng)看下一篇文章 <<Runtime --- > 消息發(fā)送>>