本篇主要講述在 OC 開發中主要涉及到的運行時機制: 運行時的工作: 運行時在 OC 中的工作:OC 語言的設計模式決定了儘可能的把程式從編譯和鏈接時推遲到運行時。只要有可能,OC 總是使用動態的方式來解決問題。這意味著 OC 語言不僅需要一個編譯器,同時也需要一個運行時系統來執行編譯好的代碼。這兒 ...
本篇主要講述在 OC 開發中主要涉及到的運行時機制:
運行時的工作:
運行時在 OC 中的工作:OC 語言的設計模式決定了儘可能的把程式從編譯和鏈接時推遲到運行時。只要有可能,OC 總是使用動態的方式來解決問題。這意味著 OC 語言不僅需要一個編譯器,同時也需要一個運行時系統來執行編譯好的代碼。這兒的運行時系統扮演的角色類似於 OC 語言的操作系統,OC 基於該系統來工作。
運行時的簡單應用:
OC 2.0運行時系統參考庫描述了OC 運行庫的數據結構和函數介面。程式可以通過這些介面來和 OC 運行時系統交互。例如:增加一個類或者方法,或者獲得所有類的定義列表等。
運行時的兩個版本:
OC 運行時系統有兩個版本,早期版本主要應用於 OC1.0中,現行版本用於 OC2.0中,在早期版本中,如果改變了類中實例變數的佈局,就必須重新編譯該類的所有子類。在現行版本中,如果改變了類中實例變數的佈局,無需重新編譯該類的任何子類。早起版本一般用於 Max OS X 系統中32位程式,此外可視為全部是現行版本。
交互途徑:
1、通過 OC 源代碼:
當編譯 OC 類和方法時,編譯器為實現語句動態特性將自動創建一些數據結構和函數。運行時系統的主要功能就是根據源代碼中的表達式發送消息。
2、通過 Foundation 框架中類 NSObject 的方法:
Cocoa 程式中絕大部分類都是 NSObject 類的子類,所以大部分都繼承了 NSObject 類的方法,因而繼承了 NSObject類的行為。然而某些情況下,NSObject 類僅僅定義了完成某件事情的模板,而沒有提供所有需要的代碼,某些 NSObject 的方法只是簡單的從運行時系統中獲得信息,從而允許對象進行一定程度的自我檢查。如:class 返回對象的類:isKindOfClass:和 isMemberOfClass:則檢查對象是否在指定的類繼承體系中;respondsToSelector:檢查對象能夠相應指定的消息;conformsToProtocol:檢查對象是否實現了指定協議類的方法;methodForSelector:則返回指定方法實現的地址。
3、直接通過調用運行時系統的函數:
運行時系統是一個公開介面的動態庫,由一些數據結構和函數的集合組成,這些數據結構和函數的聲明頭文件在/usr/include/objc 中。這些函數支持用純 C 的函數來實現和 OC 同樣的功能。浪游一些函數構成了 NSObject 類方法的基礎。這些函數使得訪問運行時系統介面和提供開發工具成為可能。儘管大部分情況下他們在 OC 程式中不是必須的,但是有時候對於 OC 這樣的程式來說某些函數是非常有用的。
運行時的消息機制:
1、獲得方法地址:
避免動態綁定的唯一方法就是取得方法的地址,並且直接像函數調用一樣調用它。當一個方法會被聯繫調用很多次,而且您希望節省每次調用方法都要發送消息的開銷時,使用方法地址來調用方法就顯得很有效。
利用 NSObject 類中的 methodForSelector:方法,可以獲得一個指向方法實現的指針,並可以使用該指針直接調用方法實現。methodForSelector:返回的指針和賦值的變數類型必須完全一致,包括方法的參數類型和返回值類型都在類型識別的考慮範圍中。
在指定的消息被重覆發送很多次時,避免動態綁定將減少大部分消息的開銷。
2、objc_msgSend 函數
在 OC 中,消息是直到運行的時候才和方法實現綁定的,編譯器會把一個消息表達式轉換成一個對消息函數objc_msgSend 的調用。該函數有兩個主要參數:消息接收者和消息對應的方法名字(也就是方法的選標)。
objc_msgSend(receiver, selector),同時接收消息中的任意數目的參數:objc_msgSend(receiver, selector, arg1, arg2,...)
該消息函數做了動態綁定所需要的一切;找到對應的方法實現-->將消息接收者對象和參數傳遞給找到的方法實現-->將方法實現的返回值作為該函數的返回值返回
當對象收到消息時,消息函數首先根據該對象的 isa 指針找到該對象所對應的類的方法表,並從表中尋找該消息對應的方法選標,如果找不到,objc_msSend 將從父類中找,知道 NSObject 類。一旦找到了選標,objc_msgSend 則以消息接收者對象為參數調用,調用該選標對應的方法實現。這就是在運行時系統中選擇方法實現的方式。在面向對象編程中一般稱作方法和消息動態綁定的過程。
為了加快消息的處理過程,運行時系統通常會將使用過的方法選標和方法實現的地址放入緩存中。每個類都有一個獨立的緩存,同時包括繼承的方法和在該類中定義的方法。消息函數會首先檢查消息接收者對象對應的類的緩存(理論上,如果一個方法被使用過一次,那麼它很可能被再次使用)。如果在緩存中已經有了需要的方法選標,則消息僅僅比函數調用慢一點點。如果程式運行了足夠長的時間,幾乎每個消息都能在緩存中找到方法實現。程式運行時,緩存也將隨著新的消息的增加而增加。
3、使用隱藏的參數
當 objc_msgSend 找到方法對應的實現時,它將直接調用該方法的實現,並將消息中所有的參數都傳遞給方法實現,同時,還將傳遞兩個隱藏的參數:
1、接受消息的對象:可以通過 self 來引用消息接收者對象
2、方法選標:通過選標_cmd 來引用方法本身。
儘管這些參數沒有被顯示聲明,但在源代碼中仍然可以引用它們。
動態方法解析:
1、動態方法解析:
@dynamic property name; 表示編譯器需動態的生成該屬性對應的方法。
可以通過實現 resolveInstanceMethod:和 resolveClassMethod:來動態的實現給定選標的對象方法或者類方法。
OC 方法可以認為是至少有兩個參數 self 和_cmd 的 C 函數。可以通過 class_addMethod 方法將一個函數加入到類的方法中,如下:
void dynamicMethodIMP(id self, SEL _cmd) {
// implementation...
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(resolveThisMethodDynamically)) {
class_addMethod([self class], sel, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
在進入消息轉發機制之前,respondsToSelector:和 instancesRespondToSelector:會被首先調用。可以在這兩個方法中為傳進來的選標提供一個 IMP。如果您實現了 resolveInstanceMethod:方法但是仍然希望正常的消息轉發機制進行,只需要返回 NO 就可以了。
2、動態載入
OC 程式可以在運行時鏈接和載入新的類和範疇類。新載入的類和在程式啟動時載入的類並沒有區別。
動態載入可以用在很多地方,例如,系統配置中的模塊就是被動態載入的。
應用場景:
在 Cocoa 環境中,動態載入一般被用來對應用程式進行定製。可以在運行時載入其他程式員編寫的模塊(和 interface Build載入定製的調色板以及系統配置程式載入定製的模塊類似)。這些模塊通過許可的方式擴展了自身的程式,而無需自己來定義或者實現。自己提供了框架,二其他程式員提供了實現。
消息轉發:
1、消息轉發
如果一個對象收到一條無法處理的消息,運行時系統會在拋出錯誤前,給該對象發送一條 forwardInvocation:消息,該消息的唯一參數是個 NSInvocation 類型的對象,該對象封裝了原始的消息和消息的參數。所以可以實現 forwardInvocation: 方法來對不能處理的消息做一些預設的處理,也可以以其它的某種方式來避免錯誤被拋出。
當一個對象沒有響應的方法實現而無法響應某消息時,運行時系統將通過 forwardInvocation: 消息通知該對象。每個對象都從 NSObject 類中集成了 forwardInvocation: 方法。然而,NSObject 中的方法實現只是簡單的調用了 doerNotRecognizeSelector:。 通過實現自己的 forwardInvocation: 方法可以在該方法實現中將消息轉發給其他對象。
消息可以通過 invokeWithTarget:方法來轉發:
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if ([someOtherObject respondsToSelector:[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}
forwardInvocation:方法就像一個不能識別的消息的分發中心,將這些消息轉發給不同接收對象。它可以將一個消息翻譯成另外一個消息,或者簡單的“吃掉”某些消息,因此沒有響應也沒有錯誤。forwardInvocation:方法也可以對不同的消息提供同樣的相應,這一切都取決於方法的具體實現。該方法所提供是將不通的對象鏈接到消息鏈的能力。
註意:forwardInvocation:方法只有在消息接受對象中無法正常響應消息時才會被調用。所以,如果您希望您的對象將 clickBtn:消息轉發給其他對象,您的對象不能有 clickBtn:方法。否則,forwardInvocation:將不可能會被調用。
2、消息轉發和多重繼承
消息轉發很像集成,並且可以用來在 OC 程式中模擬多重繼承。一個對象通過轉發來響應消息,看起來就像該對象從別的類那裡借來了或者“繼承”了方法實現一樣。通過 forwardInvocation: 方法將一個類中的消息轉發給另一個類.
3、消息代理對象
4、消息轉發和類繼承