(轉(zhuǎn))Objective-C Runtime

轉(zhuǎn)自:http://tech.glowing.com/cn/objective-c-runtime/
Objective-C
Objective-C 擴(kuò)展了 C 語(yǔ)言,并加入了面向?qū)ο筇匦院?Smalltalk 式的消息傳遞機(jī)制。而這個(gè)擴(kuò)展的核心是一個(gè)用 C 和 編譯語(yǔ)言 寫的 Runtime 庫(kù)。它是 Objective-C 面向?qū)ο蠛蛣?dòng)態(tài)機(jī)制的基石。
Objective-C 是一個(gè)動(dòng)態(tài)語(yǔ)言,這意味著它不僅需要一個(gè)編譯器,也需要一個(gè)運(yùn)行時(shí)系統(tǒng)來(lái)動(dòng)態(tài)得創(chuàng)建類和對(duì)象、進(jìn)行消息傳遞和轉(zhuǎn)發(fā)。理解 Objective-C 的 Runtime 機(jī)制可以幫我們更好的了解這個(gè)語(yǔ)言,適當(dāng)?shù)臅r(shí)候還能對(duì)語(yǔ)言進(jìn)行擴(kuò)展,從系統(tǒng)層面解決項(xiàng)目中的一些設(shè)計(jì)或技術(shù)問題。了解 Runtime ,要先了解它的核心 - 消息傳遞 (Messaging)。
消息傳遞(Messaging)
I’m sorry that I long ago coined the term “objects” for this topic because it gets many people to focus on the lesser idea. The big idea is “messaging” – that is what the kernal[sic] of Smalltalk is all about... The key in making great and growable systems is much more to design how its modules communicate rather than what their internal properties and behaviors should be.

Alan Kay 曾多次強(qiáng)調(diào) Smalltalk 的核心不是面向?qū)ο螅嫦驅(qū)ο笾皇?strong>the lesser ideas,消息傳遞才是 the big idea
在很多語(yǔ)言,比如 C ,調(diào)用一個(gè)方法其實(shí)就是跳到內(nèi)存中的某一點(diǎn)并開始執(zhí)行一段代碼。沒有任何動(dòng)態(tài)的特性,因?yàn)檫@在編譯時(shí)就決定好了。而在 Objective-C 中,[object foo]
語(yǔ)法并不會(huì)立即執(zhí)行 foo 這個(gè)方法的代碼。它是在運(yùn)行時(shí)給 object 發(fā)送一條叫 foo 的消息。這個(gè)消息,也許會(huì)由 object 來(lái)處理,也許會(huì)被轉(zhuǎn)發(fā)給另一個(gè)對(duì)象,或者不予理睬假裝沒收到這個(gè)消息。多條不同的消息也可以對(duì)應(yīng)同一個(gè)方法實(shí)現(xiàn)。這些都是在程序運(yùn)行的時(shí)候決定的。
事實(shí)上,在編譯時(shí)你寫的 Objective-C 函數(shù)調(diào)用的語(yǔ)法都會(huì)被翻譯成一個(gè) C 的函數(shù)調(diào)用 - objc_msgSend()
。比如,下面兩行代碼就是等價(jià)的:
[array insertObject:foo atIndex:5];objc_msgSend(array, @selector(insertObject:atIndex:), foo, 5);

消息傳遞的關(guān)鍵藏于 objc_object
中的 isa 指針和 objc_class
中的 class dispatch table。
objc_object
, objc_class
以及 Ojbc_method

在 Objective-C 中,類、對(duì)象和方法都是一個(gè) C 的結(jié)構(gòu)體,從 objc/objc.h
頭文件中,我們可以找到他們的定義:
struct objc_object { Class isa OBJC_ISA_AVAILABILITY;};struct objc_class { Class isa OBJC_ISA_AVAILABILITY;#if !OBJC2 Class super_class; const char *name; long version; long info; long instance_size; struct objc_ivar_list *ivars; **struct objc_method_list methodLists; *struct objc_cache cache; struct objc_protocol_list *protocols;#endif};struct objc_method_list { struct objc_method_list obsolete; int method_count;#ifdef LP64 int space;#endif / variable length structure */ struct objc_method method_list[1];};struct objc_method { SEL method_name; char method_types; / a string representing argument/return types */ IMP method_imp;};

objc_method_list
本質(zhì)是一個(gè)有 objc_method
元素的可變長(zhǎng)度的數(shù)組。一個(gè) objc_method
結(jié)構(gòu)體中有函數(shù)名,也就是SEL,有表示函數(shù)類型的字符串 (見 Type Encoding) ,以及函數(shù)的實(shí)現(xiàn)IMP。
從這些定義中可以看出發(fā)送一條消息也就 objc_msgSend
做了什么事。舉 objc_msgSend(obj, foo)
這個(gè)例子來(lái)說:
首先,通過 obj 的 isa 指針找到它的 class ;
在 class 的 method list 找 foo ;
如果 class 中沒到 foo,繼續(xù)往它的 superclass 中找 ;
一旦找到 foo 這個(gè)函數(shù),就去執(zhí)行它的實(shí)現(xiàn)IMP .

但這種實(shí)現(xiàn)有個(gè)問題,效率低。但一個(gè) class 往往只有 20% 的函數(shù)會(huì)被經(jīng)常調(diào)用,可能占總調(diào)用次數(shù)的 80% 。每個(gè)消息都需要遍歷一次 objc_method_list
并不合理。如果把經(jīng)常被調(diào)用的函數(shù)緩存下來(lái),那可以大大提高函數(shù)查詢的效率。這也就是 objc_class
中另一個(gè)重要成員 objc_cache
做的事情 - 再找到 foo 之后,把 foo 的 method_name
作為 key ,method_imp
作為 value 給存起來(lái)。當(dāng)再次收到 foo 消息的時(shí)候,可以直接在 cache 里找到,避免去遍歷 objc_method_list
.
動(dòng)態(tài)方法解析和轉(zhuǎn)發(fā)
在上面的例子中,如果 foo
沒有找到會(huì)發(fā)生什么?通常情況下,程序會(huì)在運(yùn)行時(shí)掛掉并拋出 unrecognized selector sent to … 的異常。但在異常拋出前,Objective-C 的運(yùn)行時(shí)會(huì)給你三次拯救程序的機(jī)會(huì):
Method resolution
Fast forwarding
Normal forwarding

Method Resolution
首先,Objective-C 運(yùn)行時(shí)會(huì)調(diào)用 +resolveInstanceMethod:
或者 +resolveClassMethod:
,讓你有機(jī)會(huì)提供一個(gè)函數(shù)實(shí)現(xiàn)。如果你添加了函數(shù)并返回 YES, 那運(yùn)行時(shí)系統(tǒng)就會(huì)重新啟動(dòng)一次消息發(fā)送的過程。還是以 foo
為例,你可以這么實(shí)現(xiàn):
void fooMethod(id obj, SEL _cmd) { NSLog(@"Doing foo");}+ (BOOL)resolveInstanceMethod:(SEL)aSEL{ if(aSEL == @selector(foo:)){ class_addMethod([self class], aSEL, (IMP)fooMethod, "v@:"); return YES; } return [super resolveInstanceMethod];}

Core Data 有用到這個(gè)方法。NSManagedObjects 中 properties 的 getter 和 setter 就是在運(yùn)行時(shí)動(dòng)態(tài)添加的。
如果 resolve 方法返回 NO ,運(yùn)行時(shí)就會(huì)移到下一步:消息轉(zhuǎn)發(fā)(Message Forwarding)
PS:iOS 4.3 加入很多新的 runtime 方法,主要都是以 imp 為前綴的方法,比如 imp_implementationWithBlock()
用 block 快速創(chuàng)建一個(gè) imp 。 上面的例子可以重寫成:
IMP fooIMP = imp_implementationWithBlock(^(id _self) { NSLog(@"Doing foo");});class_addMethod([self class], aSEL, fooIMP, "v@:");

Fast forwarding
如果目標(biāo)對(duì)象實(shí)現(xiàn)了 -forwardingTargetForSelector:
,Runtime 這時(shí)就會(huì)調(diào)用這個(gè)方法,給你把這個(gè)消息轉(zhuǎn)發(fā)給其他對(duì)象的機(jī)會(huì)。

  • (id)forwardingTargetForSelector:(SEL)aSelector{ if(aSelector == @selector(foo:)){ return alternateObject; } return [super forwardingTargetForSelector:aSelector];}

只要這個(gè)方法返回的不是 nil 和 self,整個(gè)消息發(fā)送的過程就會(huì)被重啟,當(dāng)然發(fā)送的對(duì)象會(huì)變成你返回的那個(gè)對(duì)象。否則,就會(huì)繼續(xù) Normal Fowarding 。
這里叫 Fast ,只是為了區(qū)別下一步的轉(zhuǎn)發(fā)機(jī)制。因?yàn)檫@一步不會(huì)創(chuàng)建任何新的對(duì)象,但下一步轉(zhuǎn)發(fā)會(huì)創(chuàng)建一個(gè) NSInvocation 對(duì)象,所以相對(duì)更快點(diǎn)。
Normal forwarding
這一步是 Runtime 最后一次給你挽救的機(jī)會(huì)。首先它會(huì)發(fā)送 -methodSignatureForSelector:
消息獲得函數(shù)的參數(shù)和返回值類型。如果 -methodSignatureForSelector:
返回 nil ,Runtime 則會(huì)發(fā)出 -doesNotRecognizeSelector:
消息,程序這時(shí)也就掛掉了。如果返回了一個(gè)函數(shù)簽名,Runtime 就會(huì)創(chuàng)建一個(gè) NSInvocation 對(duì)象并發(fā)送 -forwardInvocation:
消息給目標(biāo)對(duì)象。
NSInvocation 實(shí)際上就是對(duì)一個(gè)消息的描述,包括selector 以及參數(shù)等信息。所以你可以在 -forwardInvocation:
里修改傳進(jìn)來(lái)的 NSInvocation 對(duì)象,然后發(fā)送 -invokeWithTarget:
消息給它,傳進(jìn)去一個(gè)新的目標(biāo):

  • (void)forwardInvocation:(NSInvocation *)invocation{ SEL sel = invocation.selector; if([alternateObject respondsToSelector:sel]) { [invocation invokeWithTarget:alternateObject]; } else { [self doesNotRecognizeSelector:sel]; }}

Cocoa 里很多地方都利用到了消息傳遞機(jī)制來(lái)對(duì)語(yǔ)言進(jìn)行擴(kuò)展,如 Proxies、NSUndoManager 跟 Responder Chain。NSProxy 就是專門用來(lái)作為代理轉(zhuǎn)發(fā)消息的;NSUndoManager 截取一個(gè)消息之后再發(fā)送;而 Responder Chain 保證一個(gè)消息轉(zhuǎn)發(fā)給合適的響應(yīng)者。
總結(jié)
Objective-C 中給一個(gè)對(duì)象發(fā)送消息會(huì)經(jīng)過以下幾個(gè)步驟:
在對(duì)象類的 dispatch table 中嘗試找到該消息。如果找到了,跳到相應(yīng)的函數(shù)IMP去執(zhí)行實(shí)現(xiàn)代碼;
如果沒有找到,Runtime 會(huì)發(fā)送 +resolveInstanceMethod:
或者 +resolveClassMethod:
嘗試去 resolve 這個(gè)消息;
如果 resolve 方法返回 NO,Runtime 就發(fā)送 -forwardingTargetForSelector:
允許你把這個(gè)消息轉(zhuǎn)發(fā)給另一個(gè)對(duì)象;
如果沒有新的目標(biāo)對(duì)象返回, Runtime 就會(huì)發(fā)送 -methodSignatureForSelector:
和 -forwardInvocation:
消息。你可以發(fā)送 -invokeWithTarget:
消息來(lái)手動(dòng)轉(zhuǎn)發(fā)消息或者發(fā)送 -doesNotRecognizeSelector:
拋出異常。

利用 Objective-C 的 runtime 特性,我們可以自己來(lái)對(duì)語(yǔ)言進(jìn)行擴(kuò)展,解決項(xiàng)目開發(fā)中的一些設(shè)計(jì)和技術(shù)問題。下一篇文章,我會(huì)介紹 Method Swizzling 技術(shù)以及如何利用 Method Swizzling 做 Logging。
Reference
Message forwarding
Objective-c-messaging
The faster objc_msgSend
Understanding objective-c runtime

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,786評(píng)論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,656評(píng)論 3 419
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,697評(píng)論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評(píng)論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,855評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,254評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評(píng)論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,473評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,014評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,833評(píng)論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,016評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,273評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,680評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評(píng)論 1 288
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,730評(píng)論 3 393
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,006評(píng)論 2 374

推薦閱讀更多精彩內(nèi)容