運作時是蘋果提供的純 C 語言的開發庫,是一種非常牛逼、開發中經常用到的底層技術。
Objective-C 是一門簡單的語言,95% 是 C,隻是在語言層面上加了些關鍵字和文法,真正讓 Objective-C 如此強大的是它的運作時。它很小但卻很強大,它的核心是消息分發。
1、Messages
執行一個方法,有些語言,編譯器會執行一些額外的優化和錯誤檢查,因為調用關系很直接也很明顯。但對于消息分發來說,就不那麼明顯了。在發消息前不必知道某個對象是否能夠處理消息。你把消息發給它,它可能會處理,也可能轉給其他的Object來處理。一個消息不必對應一個方法,一個對象可能實作一個方法來處理多條消息。
在 Objective-C 中,消息是通過 <code>objc_msgSend()</code> 這個 runtime 方法及相近的方法來實作的。這個方法需要一個 target,selector,還有一些參數。理論上來說,編譯器隻是把消息分發變成 <code>objc_msgSend</code> 來執行。比如下面這兩行代碼是等價的。
2、Objects, Classes, MetaClasses
大多數面向對象的語言裡有 classes 和 objects 的概念。Objects 通過 Classes 生成。但是在 Objective-C 中,classes 本身也是 objects,也可以處理消息,這也是為什麼會有類方法和執行個體方法。具體來說,Objective-C 中的 Object 是一個結構體(struct),第一個成員是 isa,指向自己的 class。這是在 objc/objc.h 中定義的。
object 的 class 儲存了方法清單,還有指向父類的指針。但 classes 也是 objects,也會有 isa 變量,那麼它又指向哪兒呢?這裡就引出了第三個類型:metaclasses。一個 metaclass 被指向 class,class 被指向 object。它儲存了所有實作的方法清單,以及父類的 metaclass。
3、Methods, Selectors,IMPs
我們知道了運作時會發消息給對象。我們也知道一個對象的 class 儲存了方法清單。那麼這些消息是如何映射到方法的,這些方法又是如何被執行的呢?
第一個問題的答案很簡單。class 的方法清單其實是一個字典,key 為 selectors,IMPs 為 value。一個 IMP 是指向方法在記憶體中的實作。很重要的一點是,selector 和 IMP 之間的關系是在運作時才決定的,而不是編譯時。這樣我們就能玩出些花樣。
IMP 通常是指向方法的指針,第一個參數是 self,類型為 id,第二個參數是 <code>_cmd</code>,類型為 SEL,餘下的是方法的參數。這也是 self 和 <code>_cmd</code> 被定義的地方。下面示範了 Method 和 IMP。
運作時的作用:
建立、修改、自省 classes 和 objects
獲得某個類的所有成員變量
獲得某個類的所有屬性
獲得某個類的所有方法
交換方法實作
動态添加一個成員變量
動态添加一個屬性
動态添加一個方法
消息分發
1、class
class 開頭的方法是用來修改和自省 classes。方法如:
拿到一個 class 的所有内容
傳回單個内容
一些通用的自省方法
建立一個 object
2、ivar
這些方法能讓你得到名字,記憶體位址和 Objective-C type encoding。
3、method
這些方法主要用來自省,比如:
也有一些修改的方法,包括:
4、objc
一旦拿到了 object,你就可以對它做一些自省和修改。你可以 get/set ivar, 使用 <code>object_copy</code> 和 <code>object_dispose</code> 來 copy 和 free object 的記憶體。不僅是拿到一個 class,而是可以使用 <code>object_setClass</code> 來改變一個 object 的 class。
5、property
屬性儲存了很大一部分資訊。除了拿到名字,你還可以使用 <code>property_getAttributes</code> 來發現 property 的更多資訊,如傳回值、是否為 atomic、getter/setter 名字、是否為 dynamic、背後使用的 ivar 名字、是否為弱引用。
6、protocol
Protocols 有點像 classes,但是精簡版的,運作時的方法是一樣的。你可以擷取 method, property, protocol 清單, 檢查是否實作了其他的 protocol。
7、sel
可以處理 selectors,比如擷取名字,注冊一個 selector 等等。
比較基礎的一個動态特性是通過 String 來生成 Classes 和 Selectors。Cocoa 提供了 NSClassFromString 和 NSSelectorFromString 方法,使用起來很簡單:
于是我們就得到了一個 string class。接下來:
為什麼要這麼做呢?直接使用 NSString Class 不是更友善?通常情況下是,但有些場景下這個方法會很有用。首先,可以得知是否存在某個 class,如果運作時不存在該 class 的話,NSClassFromString 會傳回 nil。
另一個使用場景是根據不同的輸入傳回不同的 class 或 method。比如你在解析一些資料,每個資料項都有要解析的字元串以及自身的類型(String,Number,Array)。你可以在一個方法裡搞定這些,也可以使用多個方法。其中一個方法是擷取 type,然後使用 if 來調用比對的方法。另一種是根據 type 來生成一個 selector,然後調用之。以下是兩種實作方式:
可以看到,你可以把 7 行帶 if 的代碼變成 1 行。将來如果有新的類型,隻需增加實作方法即可,而不用再去添加新的 else if。
之前我們講過,方法由兩個部分組成。Selector 相當于一個方法的 id;IMP 是方法的實作。這樣分開的一個便利之處是 selector 和 IMP 之間的對應關系可以被改變。比如一個 IMP 可以有多個 selectors 指向它。
而 Method Swizzling 可以交換兩個方法的實作。或許你會問 “什麼情況下會需要這個呢?”。我們先來看下 Objective-C 中,兩種擴充 class 的途徑。首先是 subclassing。你可以重寫某個方法,調用父類的實作,這也意味着你必須使用這個 subclass 的執行個體,但如果繼承了某個 Cocoa class,而 Cocoa 又傳回了原先的 class(比如 NSArray)。這種情況下,你會想添加一個方法到 NSArray,也就是使用 Category。99% 的情況下這是 OK 的,但如果你重寫了某個方法,就沒有機會再調用原先的實作了。
Method Swizzling 可以搞定這個問題。你可以重寫某個方法而不用繼承,同時還可以調用原先的實作。通常的做法是在 category 中添加一個方法(當然也可以是一個全新的 class)。可以通過 <code>method_exchangeImplementations</code> 這個運作時方法來交換實作。來看一個 demo,這個 demo 示範了如何重寫 <code>addObject:</code> 方法來紀錄每一個新添加的對象。
我們把方法交換放到了 load 中,這個方法隻會被調用一次,而且是運作時載入。如果指向臨時用一下,可以放到别的地方。注意到一個很明顯的遞歸調用 <code>logAddObject:</code>。這也是 Method Swizzling 容易把我們搞混的地方,因為我們已經交換了方法的實作,是以其實調用的是 <code>addObject:</code>
我們可以在運作時建立新的 class,這個特性用得不多,但其實它還是很強大的。你能通過它建立新的子類,并添加新的方法。
但這樣的一個子類有什麼用呢?别忘了 Objective-C 的一個關鍵點:object 内部有一個叫做 isa 的變量指向它的 class。這個變量可以被改變,而不需要重新建立。然後就可以添加新的 ivar 和方法了。可以通過以下指令來修改一個 object 的 class.
這可以用在 Key Value Observing。當你開始 observing an object 時,Cocoa 會建立這個 object 的 class 的 subclass,然後将這個 object 的 isa 指向新建立的 subclass。
目前為止,我們讨論了方法交換,以及已有方法的處理。那麼當你發送了一個 object 無法處理的消息時會發生什麼呢?很明顯,"it breaks"。大多數情況下确實如此,但 Cocoa 和 runtime 也提供了一些應對方法。
首先是動态方法處理。通常來說,處理一個方法,運作時尋找比對的 selector 然後執行之。有時,你隻想在運作時才建立某個方法,比如有些資訊隻有在運作時才能得到。要實作這個效果,你需要重寫 <code>+resolveInstanceMethod:</code> 和/或 <code>+resolveClassMethod:</code>。如果确實增加了一個方法,記得傳回 YES。
那 Cocoa 在什麼場景下會使用這些方法呢?Core Data 用得很多。NSManagedObjects 有許多在運作時添加的屬性用來處理 get/set 屬性和關系。
如果 resolve method 傳回 NO,運作時就進入下一步驟:消息轉發。有兩種常見用例。1) 将消息轉發到另一個可以處理該消息的 object。2) 将多個消息轉發到同一個方法。
消息轉發分兩步。首先,運作時調用 <code>-forwardingTargetForSelector:</code>,如果隻是想把消息發送到另一個 object,那麼就使用這個方法,因為更高效。如果想要修改消息,那麼就要使用 <code>-forwardInvocation:</code>,運作時将消息打包成 NSInvocation,然後傳回給你處理。處理完之後,調用 <code>invokeWithTarget:</code>。
Cocoa 有幾處地方用到了消息轉發,主要的兩個地方是代理(Proxies)和響應鍊(Responder Chain)。NSProxy 是一個輕量級的 class,它的作用就是轉發消息到另一個 object。如果想要惰性加載 object 的某個屬性會很有用。NSUndoManager 也有用到,不過是截取消息,之後再執行,而不是轉發到其他的地方。
響應鍊是關于 Cocoa 如何處理與發送事件與行為到對應的對象。比如說,使用 Cmd+C 執行了 copy 指令,會發送 <code>-copy:</code> 到響應鍊。首先是 First Responder,通常是目前的 UI。如果沒有處理該消息,則轉發到下一個 <code>-nextResponder</code>。這麼一直下去直到找到能夠處理該消息的 object,或者沒有找到,報錯。
iOS 4.3 帶來了很多新的 runtime 方法。除了對 properties 和 protocols 的加強,還帶來一組新的以 imp 開頭的方法。通常一個 IMP 是一個指向方法實作的指針,頭兩個參數為 object(self) 和 selector(_cmd)。iOS 4.0 和 Mac OS X 10.6 帶來了 block,<code>imp_implementationWithBlock()</code> 能讓我們使用 block 作為 IMP,下面這個代碼片段展示了如何使用 block 來添加新的方法。
可以看到,Objective-C 表面看起來挺簡單,但還是很靈活的,可以帶來很多可能性。動态語言的優勢在于在不擴充語言本身的情況下做很多很靈巧的事情。比如 Key Value Observing,提供了優雅的 API 可以與已有的代碼無縫結合,而不需要新增語言級别的特性。
函數方法
向分類中添加屬性
包含運作時頭檔案
1、添加 OC 對象類型屬性
2、添加基本資料類型類屬性
動态擷取對象屬性
1、<code>objc_property_t</code> 方式擷取到的屬性值包含分類中添加的屬性,屬性名不帶下劃線(_),無法擷取到通路器方法添加的屬性。

2、<code>Ivar</code> 方式擷取到的屬性值不包含分類中添加的屬性,屬性名前帶下劃線(_),可以擷取到通路器方法添加的屬性。