深入Objective-C的動態(tài)特性
Objective-C具有相當(dāng)多的動態(tài)特性,基本的,也是經(jīng)常被提到和用到的有動態(tài)類型(Dynamic typing),動態(tài)綁定(Dynamic binding)和動態(tài)加載(Dynamic loading)。
這些動態(tài)特性都是在Cocoa程序開發(fā)時非常常用的語言特性,而在這之后,OC在底層也提供了相當(dāng)豐富的運行時的特性,比如枚舉類屬性方法、獲取方法實現(xiàn)等等。雖然在平常的Cocoa開發(fā)中這些較底層的運行特性基本用不著,但是在某些情況下如果你知道這些特性并合理加以運用的話,往往能事半功倍~
動態(tài)特性基礎(chǔ)
1、動態(tài)類型
即運行時再決定對象的類型。這類動態(tài)特性在日常應(yīng)用中非常常見,簡單說就是id類型。id類型即通用的對象類,任何對象都可以被id指針?biāo)?,而在實際使用中,往往使用introspection來確定該對象的實際所屬類:
- id obj = someInstance;
- if ([obj isKindOfClass:someClass])
- {
- someClass *classSpecifiedInstance = (someClass *)obj;
- // Do Something to classSpecifiedInstance which now is an instance of someClass
- //...
- }
-isMemberOfClass: 是 NSObject 的方法,用以確定某個 NSObject 對象是否是某個類的成員。與之相似的為 -isKindOfClass:,可以用以確定某個對象是否是某個類或其子類的成員。這兩個方法為典型的introspection方法。在確定對象為某類成員后,可以安全地進行強制轉(zhuǎn)換,繼續(xù)之后的工作。 2、動態(tài)綁定 基于動態(tài)類型,在某個實例對象被確定后,其類型便被確定了。該對象對應(yīng)的屬性和響應(yīng)的消息也被完全確定,這就是動態(tài)綁定。在繼續(xù)之前,需要明確Objective-C中消息的概念。由于OC的動態(tài)特性,在OC中其實很少提及“函數(shù)”的概念,傳統(tǒng)的函數(shù)一般在編譯時就已經(jīng)把參數(shù)信息和函數(shù)實現(xiàn)打包到編譯后的源碼中了,而在OC中最常使用的是消息機制。調(diào)用一個實例的方法,所做的是向該實例的指針發(fā)送消息,實例在收到消息后,從自身的實現(xiàn)中尋找響應(yīng)這條消息的方法。 動態(tài)綁定所做的,即是在實例所屬類確定后,將某些屬性和相應(yīng)的方法綁定到實例上。這里所指的屬性和方法當(dāng)然包括了原來沒有在類中實現(xiàn)的,而是在運行時才需要的新加入的實現(xiàn)。在Cocoa層,我們一般向一個NSObject對象發(fā)送-respondsToSelector:或者-instancesRespondToSelector:等來確定對象是否可以對某個SEL做出響應(yīng),而在OC消息轉(zhuǎn)發(fā)機制被觸發(fā)之前,對應(yīng)的類的+resolveClassMethod:和+resolveInstanceMethod:將會被調(diào)用,在此時有機會動態(tài)地向類或者實例添加新的方法,也即類的實現(xiàn)是可以動態(tài)綁定的。一個例子: 當(dāng)然也可以在任意需要的地方調(diào)用class_addMethod或者method_setImplementation(前者添加實現(xiàn),后者替換實現(xiàn)),來完成動態(tài)綁定的需求。 3、動態(tài)加載 根據(jù)需求加載所需要的資源,這點很容易理解,對于iOS開發(fā)來說,基本就是根據(jù)不同的機型做適配。最經(jīng)典的例子就是在Retina設(shè)備上加載@2x的圖片,而在老一些的普通屏設(shè)備上加載原圖。隨著Retina iPad的推出,和之后可能的Retina Mac的出現(xiàn),這個特性相信會被越來越多地使用。 深入運行時特性 基本的動態(tài)特性在常規(guī)的Cocoa開發(fā)中非常常用,特別是動態(tài)類型和動態(tài)綁定。由于Cocoa程序大量地使用Protocol-Delegate的設(shè)計模式,因此絕大部分的delegate指針類型必須是id,以滿足運行時delegate的動態(tài)替換(在Java里這個設(shè)計模式被叫做Strategy?不是很懂Java,求糾正)。而Objective-C還有一些高級或者說更底層的運行時特性,在一般的Cocoa開發(fā)中較為少見,基本被運用與編寫OC和其他語言的接口上。但是如果有所了解并使用得當(dāng)?shù)脑?,在Cocoa開發(fā)中往往可以輕易解決一些棘手問題。 這類運行時特性大多由/usr/lib/libobjc.A.dylib這個動態(tài)庫提供,里面包括了對于類、實例成員、成員方法和消息發(fā)送的很多API,包括獲取類實例變量列表,替換類中的方法,為類成員添加變量,動態(tài)改變方法實現(xiàn)等,十分強大。完整的API列表和手冊可以在這里找到。雖然文檔開頭表明是對于Mac OS X Objective-C 2.0適用,但是由于這些是OC的底層方法,因此對于iOS開發(fā)來說也是完全相同的。 一個簡單的例子,比如在開發(fā)Universal應(yīng)用或者游戲時,如果使用IB構(gòu)建了大量的自定義的UI,那么在由iPhone版轉(zhuǎn)向iPad版的過程中所面臨的一個重要問題就是如何從不同的nib中加載界面。在iOS5之前,所有的UIViewController在使用默認(rèn)的界面加載時(init或者initWithNibName:bundle:),都會走-loadNibNamed:owner:options:。而因為我們無法拿到-loadNibNamed:owner:options的實現(xiàn),因此對其重載是比較困難而且存在風(fēng)險的。因此在做iPad版本的nib時,一個簡單的辦法是將所有的nib的命名方式統(tǒng)一,然后使用自己實現(xiàn)的新的類似-loadNibNamed:owner:options的方法將原方法替換掉,同時保證非iPad的設(shè)備還走原來的loadNibNamed:owner:options方法。使用OC運行時特性可以較簡單地完成這一任務(wù)。 代碼如下,在程序運行時調(diào)用+swizze,交換自己實現(xiàn)的loadPadNibNamed:owner:options和系統(tǒng)的loadNibNamed:owner:options,之后所有的loadNibNamed:owner:options消息都將會發(fā)為loadPadNibNamed:owner:options,由自己的代碼進行處理。 loadPadNibNamed:owner:options的實現(xiàn)如下,注意在其中的loadPadNibNamed:owner:options由于之前已經(jīng)進行了交換,因此實際會發(fā)送為系統(tǒng)的loadNibNamed:owner:options。以此完成了對不同資源的加載。 當(dāng)然這只是一個簡單的例子,而且這個功能也可以通過別的方法來實現(xiàn)。比如添加UIViewController的類別來重載init,但是這樣的重載會比較危險,因為你UIViewController的實現(xiàn)你無法完全知道,因此可能會由于重載導(dǎo)致某些本來應(yīng)有的init代碼沒有覆蓋,從而出現(xiàn)不可預(yù)測的錯誤。當(dāng)然在上面這個例子中重載VC的init的話沒有什么問題(因為對于VC,init的方法會被自動轉(zhuǎn)發(fā)為loadNibNamed:owner:options,因此init方法并沒有做其他更復(fù)雜的事情,因此只要初始化VC時使用的都是init的話就沒有問題)。但是并不能保證這樣的重載對于其他類也是可行的。因此對于實現(xiàn)未知的系統(tǒng)方法,使用上面的運行時交換方法會是一個不錯的選擇~