1. 程式人生 > >Objective-C runtime機制

Objective-C runtime機制

Objective-C runtime機制

先來看看怎麼理解發送訊息的含義:

曾經覺得Objc特別方便上手,面對著 Cocoa 中大量 API,只知道簡單的查文件和呼叫。還記得初學 Objective-C 時把[receiver message]當成簡單的方法呼叫,而無視了“傳送訊息”這句話的深刻含義。於是[receiver message]會被編譯器轉化為:
objc_msgSend(receiver, selector)
如果訊息含有引數,則為:
objc_msgSend(receiver, selector, arg1, arg2, …)

如果訊息的接收者能夠找到對應的selector,那麼就相當於直接執行了接收者這個物件的特定方法;否則,訊息要麼被轉發,或是臨時向接收者動態新增這個selector對應的實現內容,要麼就乾脆玩完崩潰掉。

現在可以看出[receiver message]真的不是一個簡簡單單的方法呼叫。因為這只是在編譯階段確定了要向接收者傳送message這條訊息,而receive將要如何響應這條訊息,那就要看執行時發生的情況來決定了。

Objective-C 的 Runtime 鑄就了它動態語言的特性,這些深層次的知識雖然平時寫程式碼用的少一些,但是卻是每個 Objc 程式設計師需要了解的。

Objc Runtime使得C具有了面向物件能力,在程式執行時建立,檢查,修改類、物件和它們的方法。可以使用runtime的一系列方法實現。

順便附上OC中一個類的資料結構 /usr/include/objc/runtime.h
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; //isa指標指向Meta Class,因為Objc的類的本身也是一個Object,為了處理這個關係,runtime就創造了Meta Class,當給類傳送[NSObject alloc]這樣訊息時,實際上是把這個訊息發給了Class Object

#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父類
const char *name OBJC2_UNAVAILABLE; // 類名
long version OBJC2_UNAVAILABLE; // 類的版本資訊,預設為0
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; // 方法快取,物件接到一個訊息會根據isa指標查詢訊息物件,這時會在method       Lists中遍歷,如果cache了,常用的方法呼叫時就能夠提高呼叫的效率。
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 協議連結串列
#endif

} OBJC2_UNAVAILABLE;

OC中一個類的物件例項的資料結構(/usr/include/objc/objc.h):

typedef struct objc_class *Class;

/// Represents an instance of a class.

struct objc_object {

    Class isa  OBJC_ISA_AVAILABILITY;

};

/// A pointer to an instance of a class.

typedef struct objc_object *id;

向object傳送訊息時,Runtime庫會根據object的isa指標找到這個例項object所屬於的類,然後在類的方法列表以及父類方法列表尋找對應的方法執行。id是一個objc_object結構型別的指標,這個型別的物件能夠轉換成任何一種物件。

然後再來看看訊息傳送的函式:objc_msgSend函式

在引言中已經對objc_msgSend進行了一點介紹,看起來像是objc_msgSend返回了資料,其實objc_msgSend從不返回資料而是你的方法被呼叫後返回了資料。下面詳細敘述下訊息傳送步驟:

檢測這個 selector 是不是要忽略的。比如 Mac OS X 開發,有了垃圾回收就不理會 retain,release 這些函數了。
檢測這個 target 是不是 nil 物件。ObjC 的特性是允許對一個 nil 物件執行任何一個方法不會 Crash,因為會被忽略掉。
如果上面兩個都過了,那就開始查詢這個類的 IMP,先從 cache 裡面找,完了找得到就跳到對應的函式去執行。
如果 cache 找不到就找一下方法分發表。
如果分發表找不到就到超類的分發表去找,一直找,直到找到NSObject類為止。
如果還找不到就要開始進入動態方法解析了,後面會提到。

後面還有:
動態方法解析resolveThisMethodDynamically
訊息轉發forwardingTargetForSelector

runtime使用例子

[例項物件或【類 class】 performSelector:@selector(方法)];

objc_msgSend(例項物件或類,@select(方法),引數1,引數2);
通過物件的isa指標找到對應的class,先從cache中通過SEL查詢對應函式方法,找到則通過method中的函式指標跳轉到對應函式中執行。如果cache中未找到則再去methodList中查詢,找到了會將method加入到cache中,以便下次查詢;不行再去superClass中查詢,若能找到加入到cache中去在跳轉。

使用方法:獲取要交換的兩個方法
// 獲取類方法,用Method接受一下
// class_getClassMethod:獲取哪個類方法
// SEL :@selector獲取方法編號,根據SEL就能去對應的類找方法。
method_exchangeImplementations 交換兩個方法的實現

運用在load方法中

詳情可參考
[1]: http://www.jianshu.com/p/620022378e97