Objective-C Runtime(轉)

來源:互聯網
上載者:User

標籤:

Objective-C

Objective-C 擴充了 C 語言,並加入了物件導向特性和 Smalltalk 式的訊息傳遞機制。而這個擴充的核心是一個用 C 和 編譯語言 寫的 Runtime 庫。它是 Objective-C 物件導向和動態機制的基石。

Objective-C 是一個動態語言,這意味著它不僅需要一個編譯器,也需要一個運行時系統來動態得建立類和對象、進行訊息傳遞和轉寄。理解 Objective-C 的 Runtime 機制可以幫我們更好的瞭解這個語言,適當的時候還能對語言進行擴充,從系統層面解決項目中的一些設計或技術問題。瞭解 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 曾多次強調 Smalltalk 的核心不是物件導向,物件導向只是 the lesser ideas,訊息傳遞才是 the big idea。

在很多語言,比如 C ,調用一個方法其實就是跳到記憶體中的某一點並開始執行一段代碼。沒有任何動態特性,因為這在編譯時間就決定好了。而在 Objective-C 中,[object foo] 文法並不會立即執行 foo 這個方法的代碼。它是在運行時給 object 發送一條叫 foo 的訊息。這個訊息,也許會由 object 來處理,也許會被轉寄給另一個對象,或者不予理睬假裝沒收到這個訊息。多條不同的訊息也可以對應同一個方法實現。這些都是在程式啟動並執行時候決定的。

事實上,在編譯時間你寫的 Objective-C 函數調用的文法都會被翻譯成一個 C 的函數調用 - objc_msgSend() 。比如,下面兩行代碼就是等價的:

[array insertObject:foo atIndex:5];objc_msgSend(array, @selector(insertObject:atIndex:), foo, 5);

訊息傳遞的關鍵藏於 objc_object 中的 isa 指標和 objc_class 中的 class dispatch table。

objc_objectobjc_class 以及  Ojbc_method

在 Objective-C 中,類、對象和方法都是一個 C 的結構體,從 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 本質是一個有 objc_method 元素的可變長度的數組。一個 objc_method 結構體中有函數名,也就是SEL,有表示函數類型的字串 (見 Type Encoding) ,以及函數的實現IMP。

從這些定義中可以看出發送一條訊息也就 objc_msgSend 做了什麼事。舉 objc_msgSend(obj, foo) 這個例子來說:

  1. 首先,通過 obj 的 isa 指標找到它的 class ;
  2. 在 class 的 method list 找 foo ;
  3. 如果 class 中沒到 foo,繼續往它的 superclass 中找 ;
  4. 一旦找到 foo 這個函數,就去執行它的實現IMP .

但這種實現有個問題,效率低。但一個 class 往往只有 20% 的函數會被經常調用,可能佔總調用次數的 80% 。每個訊息都需要遍曆一次 objc_method_list 並不合理。如果把經常被調用的函數緩衝下來,那可以大大提高函數查詢的效率。這也就是 objc_class 中另一個重要成員 objc_cache 做的事情 - 再找到 foo 之後,把 foo 的 method_name 作為 key ,method_imp 作為 value 給存起來。當再次收到 foo 訊息的時候,可以直接在 cache 裡找到,避免去遍曆 objc_method_list.

動態方法解析和轉寄

在上面的例子中,如果 foo 沒有找到會發生什嗎?通常情況下,程式會在運行時掛掉並拋出 unrecognized selector sent to … 的異常。但在異常拋出前,Objective-C 的運行時會給你三次拯救程式的機會:

  1. Method resolution
  2. Fast forwarding
  3. Normal forwarding
Method Resolution

首先,Objective-C 運行時會調用 +resolveInstanceMethod: 或者 +resolveClassMethod:,讓你有機會提供一個函數實現。如果你添加了函數並返回 YES, 那運行時系統就會重新啟動一次訊息發送的過程。還是以 foo 為例,你可以這麼實現:

void fooMethod(id obj, SEL _cmd)  {    NSLog(@"Doing foo");}+ (BOOL)resolveInstanceMethod:(SEL)aSEL{    if(aSEL == @selector(foo:)){        class_addMethod([self class], aSEL, (IMP)fooMethod, "[email protected]:");        return YES;    }    return [super resolveInstanceMethod];}

Core Data 有用到這個方法。NSManagedObjects 中 properties 的 getter 和 setter 就是在運行時動態添加的。

如果 resolve 方法返回 NO ,運行時就會移到下一步:訊息轉寄(Message Forwarding)。

PS:iOS 4.3 加入很多新的 runtime 方法,主要都是以 imp 為首碼的方法,比如 imp_implementationWithBlock() 用 block 快速建立一個 imp 。 
上面的例子可以重寫成:

IMP fooIMP = imp_implementationWithBlock(^(id _self) {      NSLog(@"Doing foo");});class_addMethod([self class], aSEL, fooIMP, "[email protected]:");  
Fast forwarding

如果目標對象實現了 -forwardingTargetForSelector: ,Runtime 這時就會調用這個方法,給你把這個訊息轉寄給其他對象的機會。

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

只要這個方法返回的不是 nil 和 self,整個訊息發送的過程就會被重啟,當然發送的對象會變成你返回的那個對象。否則,就會繼續 Normal Fowarding 。

這裡叫 Fast ,只是為了區別下一步的轉寄機制。因為這一步不會建立任何新的對象,但下一步轉寄會建立一個 NSInvocation 對象,所以相對更快點。

Normal forwarding

這一步是 Runtime 最後一次給你挽救的機會。首先它會發送 -methodSignatureForSelector: 訊息獲得函數的參數和傳回值類型。如果 -methodSignatureForSelector: 返回 nil ,Runtime 則會發出 -doesNotRecognizeSelector: 訊息,程式這時也就掛掉了。如果返回了一個函數簽名,Runtime 就會建立一個 NSInvocation 對象並發送 -forwardInvocation: 訊息給目標對象。

NSInvocation 實際上就是對一個訊息的描述,包括selector 以及參數等資訊。所以你可以在 -forwardInvocation: 裡修改傳進來的 NSInvocation 對象,然後發送 -invokeWithTarget: 訊息給它,傳進去一個新的目標:

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

Cocoa 裡很多地方都利用到了訊息傳遞機制來對語言進行擴充,如 Proxies、NSUndoManager 跟 Responder Chain。NSProxy 就是專門用來作為代理轉寄訊息的;NSUndoManager 截取一個訊息之後再發送;而 Responder Chain 保證一個訊息轉寄給合適的響應者。

總結

Objective-C 中給一個對象發送訊息會經過以下幾個步驟:

  1. 在對象類的 dispatch table 中嘗試找到該訊息。如果找到了,跳到相應的函數IMP去執行實現代碼;
  2. 如果沒有找到,Runtime 會發送 +resolveInstanceMethod: 或者 +resolveClassMethod: 嘗試去 resolve 這個訊息;
  3. 如果 resolve 方法返回 NO,Runtime 就發送 -forwardingTargetForSelector:允許你把這個訊息轉寄給另一個對象;
  4. 如果沒有新的目標對象返回, Runtime 就會發送 -methodSignatureForSelector:和 -forwardInvocation: 訊息。你可以發送 -invokeWithTarget: 訊息來手動轉寄訊息或者發送 -doesNotRecognizeSelector: 拋出異常。

利用 Objective-C 的 runtime 特性,我們可以自己來對語言進行擴充,解決項目開發中的一些設計和技術問題。下一篇文章,我會介紹 Method Swizzling 技術以及如何利用 Method Swizzling 做 Logging。

Reference

Message forwarding

Objective-c-messaging

The faster objc_msgSend

Understanding objective-c runtime

Objective-C Runtime(轉)

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.