本文詳細(xì)整理了 Cocoa 的 Runtime 系統(tǒng)的知識(shí)腕让,它使得 Objective-C 如虎添翼承粤,具備了靈活的動(dòng)態(tài)特性妆距,使這門(mén)古老的語(yǔ)言煥發(fā)生機(jī)校坑。主要內(nèi)容如下:
- 引言
- 簡(jiǎn)介
- 與 Runtime 交互
- Rumtime 術(shù)語(yǔ)
- 消息
- 動(dòng)態(tài)方法解析
- 消息轉(zhuǎn)發(fā)
- 健壯的實(shí)例變量(Non Fragile ivars)
- Objective-C Associated Objects
- Method Swizzling
- 總結(jié)
本文為轉(zhuǎn)載轧飞,出處鏈接后續(xù)補(bǔ)上。撒踪。
引言
曾經(jīng)覺(jué)得Objc特變方便上手过咬,面對(duì)著Cocoa中大量API,只知道簡(jiǎn)單地查看文檔和調(diào)用制妄。還記得初學(xué) Objective-C 時(shí)把[receiver message]
當(dāng)成簡(jiǎn)單地方法調(diào)用掸绞,而無(wú)視了“發(fā)送消息”這句話的深刻含義。于是[receiver message]
會(huì)被編譯器轉(zhuǎn)化為:
objc_mesgSend(receiver,selector)
如果消息含有參數(shù)耕捞,則為:
objc_mesgSend(receiver, selector, arg1, arg2, ...)
如果消息的接收者能夠找到對(duì)應(yīng)的selector
衔掸,那么就相當(dāng)于直接執(zhí)行了接收者這個(gè)對(duì)象的特定方法;否則俺抽,消息要么被轉(zhuǎn)發(fā)敞映,或是臨時(shí)向接收者動(dòng)態(tài)添加這個(gè)selector
對(duì)應(yīng)的實(shí)現(xiàn)內(nèi)容,要么就干脆玩完奔潰掉磷斧。
現(xiàn)在可以看出[receiver message]
真的不是一個(gè)簡(jiǎn)簡(jiǎn)單單的方法調(diào)用振愿。因?yàn)檫@只是在編譯階段確定了要向接收者發(fā)送message
這條消息,而receive
將要如何響應(yīng)這條消息弛饭,那就要看運(yùn)行時(shí)發(fā)生的情況來(lái)決定了冕末。
Objective-C 的 Runtime 鑄就了它動(dòng)態(tài)語(yǔ)言的特性, 這些深層次的只是雖然平時(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"和"legacy"。我們現(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ǔ)
還記得引言中得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 struc objc_object *id;
那objc_object
又是啥呢:
struct objc_object { Class isa;};
objc_object
結(jié)構(gòu)體包含一個(gè)isa
指針就可以順藤摸瓜找到對(duì)象所屬的類(lèi)而涉。
Class
之所以說(shuō)isa
是指針是因?yàn)?code>Class其實(shí)是一個(gè)指向objc_class
結(jié)構(gòu)體的指針:
typedef struct objc_class *Class;
而objc_class
就是我們魔道的那個(gè)瓜,里面的東西多著呢:
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;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#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;
}