Objective-C總Runtime的那點事兒(一)訊息機制【轉】,objective-cruntime
RunTime簡稱運行時。就是系統在啟動並執行時候的一些機制,其中最主要的是訊息機制。對於C語言,函數的調用在編譯的時候會決定調用哪個函數( C語言的函數調用請看這裡 )。編譯完成之後直接順序執行,無任何二義性。OC的函數調用成為訊息發送。屬於動態調用過程。在編譯的時候並不能決定真正調用哪個函數(事實證明,在編 譯階段,OC可以調用任何函數,即使這個函數並未實現,只要申明過就不會報錯。而C語言在編譯階段就會報錯)。只有在真正啟動並執行時候才會根據函數的名稱找 到對應的函數來調用。
那OC是怎麼實現動態調用的呢?下面我們來看看OC通過發送訊息來達到動態調用的秘密。假如在OC中寫了這樣的一個代碼:
其中obj是一個對象,makeText是一個函數名稱。對於這樣一個簡單的調用。在編譯時間RunTime會將上述代碼轉化成
1 |
objc_msgSend(obj,@selector(makeText)); |
首先我們來看看obj這個對象,iOS中的obj都繼承於NSObject。
123 |
@interface NSObject <nsobject> { Class isa OBJC_ISA_AVAILABILITY; }</nsobject> |
在NSObjcet中存在一個Class的isa指標。然後我們看看Class:
1234567891011121314 |
typedef struct objc_class *Class; struct objc_class { Class isa; // 指向metaclass Class super_class ; // 指向其父類 const char *name ; // 類名 long version ; // 類的版本資訊,初始化預設為0,可以通過runtime函數class_setVersion和class_getVersion進行修改、讀取 long info; // 一些標識資訊,如CLS_CLASS (0x1L) 表示該類為普通 class ,其中包含對象方法和成員變數;CLS_META (0x2L) 表示該類為 metaclass,其中包含類方法; long instance_size ; // 該類的執行個體變數大小(包括從父類繼承下來的執行個體變數); struct objc_ivar_list *ivars; // 用於儲存每個成員變數的地址 struct objc_method_list **methodLists ; // 與 info 的一些標誌位有關,如CLS_CLASS (0x1L),則儲存物件方法,如CLS_META (0x2L),則儲存類方法; struct objc_cache *cache; // 指向最近使用的方法的指標,用於提升效率; struct objc_protocol_list *protocols; // 儲存該類遵守的協議 } |
我們可以看到,對於一個Class類中,存在很多東西,下面我來一一解釋一下:
Class isa:指向metaclass,也就是靜態Class。一般一個Obj對象中的isa會指向普通的Class,這個Class中儲存普通成員變數和對 象方法(“-”開頭的方法),普通Class中的isa指標指向靜態Class,靜態Class中儲存static類型成員變數和類方法(“+”開頭的方 法)。
Class super_class:指向父類,如果這個類是根類,則為NULL。
下面一張圖片很好的描述了類和對象的繼承關係:
注意:所有metaclass中isa指標都指向跟metaclass。而跟metaclass則指向自身。Root metaclass是通過繼承Root class產生的。與root class結構體成員一致,也就是前面提到的結構。不同的是Root metaclass的isa指標指向自身。
Class類中其他的成員這裡就先不做過多解釋了,下面我們來看看:
@selector (makeText):這是一個SEL方法選取器。SEL其主要作用是快速的通過方法名字(makeText)尋找到對應方法的函數指標,然後調用其函 數。SEL其本身是一個Int類型的一個地址,地址中存放著方法的名字。對於一個類中。每一個方法對應著一個SEL。所以iOS類中不能存在2個名稱相同 的方法,即使參數類型不同,因為SEL是根據方法名字產生的,相同的方法名稱只能對應一個SEL。
下面我們就來看看具體訊息發送之後是怎麼來動態尋找對應的方法的。
首先,編譯器將代碼[obj makeText];轉化為objc_msgSend(obj, @selector (makeText));,在objc_msgSend函數中。首先通過obj的isa指標找到obj對應的class。在Class中先去cache中 通過SEL尋找對應函數method(猜測cache中method列表是以SEL為key通過hash表來儲存的,這樣能提高函數尋找速度),若 cache中未找到。再去methodList中尋找,若methodlist中未找到,則取superClass中尋找。若能找到,則將method加 入到cache中,以方便下次尋找,並通過method中的函數指標跳轉到對應的函數中去執行。