Objective-C 動態特性應用指南
Objective-C具有相當多的動態特性,基本的,也是經常被提到和用到的有動態型別(Dynamic typing),動態繫結(Dynamic binding)和動態載入(Dynamic loading)。
這些動態特性都是在Cocoa程式開發時非常常用的語言特性,而在這之後,OC在底層也提供了相當豐富的執行時的特性,比如列舉類屬性方法、獲取方 法實現等等。雖然在平常的Cocoa開發中這些較底層的執行特性基本用不著,但是在某些情況下如果你知道這些特性併合理加以運用的話,往往能事半功倍~
動態特性基礎
1、動態型別
即執行時再決定物件的型別。這類動態特性在日常應用中非常常見,簡單說就是id型別。id型別即通用的物件類,任何物件都可以被id指標所指,而在實際使用中,往往使用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方法。在確定物件為某類成員後,可以安全地進行強制轉換,繼續之後的工作。
2、動態繫結
基於動態型別,在某個例項物件被確定後,其型別便被確定了。該物件對應的屬性和響應的訊息也被完全確定,這就是動態繫結。在繼續之前,需要明確 Objective-C中訊息的概念。由於OC的動態特性,在OC中其實很少提及“函式”的概念,傳統的函式一般在編譯時就已經把引數資訊和函式實現打包 到編譯後的原始碼中了,而在OC中最常使用的是訊息機制。呼叫一個例項的方法,所做的是向該例項的指標傳送訊息,例項在收到訊息後,從自身的實現中尋找響應 這條訊息的方法。
動態繫結所做的,即是在例項所屬類確定後,將某些屬性和相應的方法繫結到例項上。這裡所指的屬性和方法當然包括了原來沒有在類中實現的,而是在執行 時才需要的新加入的實現。在Cocoa層,我們一般向一個NSObject物件傳送-respondsToSelector:或者 -instancesRespondToSelector:等來確定物件是否可以對某個SEL做出響應,而在OC訊息轉發機制被觸發之前,對應的類 的+resolveClassMethod:和+resolveInstanceMethod:將會被呼叫,在此時有機會動態地向類或者例項新增新的方 法,也即類的實現是可以動態繫結的。一個例子:
void dynamicMethodIMP(id self, SEL _cmd) { // implementation .... } //該方法在OC訊息轉發生效前被呼叫 + (BOOL) resolveInstanceMethod:(SEL)aSEL { if (aSEL == @selector(resolveThisMethodDynamically)) { //向[self class]中新加入返回為void的實現,SEL名字為aSEL,實現的具體內容為dynamicMethodIMP class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, “v@:”); return YES; } return [super resolveInstanceMethod:aSel]; }
當然也可以在任意需要的地方呼叫class_addMethod或者method_setImplementation(前者新增實現,後者替換實現),來完成動態繫結的需求。
3、動態載入
根據需求載入所需要的資源,這點很容易理解,對於iOS開發來說,基本就是根據不同的機型做適配。最經典的例子就是在Retina裝置上載入@2x 的圖片,而在老一些的普通屏裝置上載入原圖。隨著Retina iPad的推出,和之後可能的Retina Mac的出現,這個特性相信會被越來越多地使用。
深入執行時特性
基本的動態特性在常規的Cocoa開發中非常常用,特別是動態型別和動態繫結。由於Cocoa程式大量地使用Protocol-Delegate的 設計模式,因此絕大部分的delegate指標型別必須是id,以滿足執行時delegate的動態替換(在Java裡這個設計模式被叫做 Strategy?不是很懂Java,求糾正)。而Objective-C還有一些高階或者說更底層的執行時特性,在一般的Cocoa開發中較為少見,基 本被運用與編寫OC和其他語言的介面上。但是如果有所瞭解並使用得當的話,在Cocoa開發中往往可以輕易解決一些棘手問題。
這類執行時特性大多由/usr/lib/libobjc.A.dylib這個動態庫提供,裡面包括了對於類、例項成員、 成員方法和訊息傳送的很多API,包括獲取類例項變數列表,替換類中的方法,為類成員新增變數,動態改變方法實現等,十分強大。完整的API列表和手冊可 以在這裡找到。雖然文件開頭表明是對於Mac OS X Objective-C 2.0適用,但是由於這些是OC的底層方法,因此對於iOS開發來說也是完全相同的。
一個簡單的例子,比如在開發Universal應用或者遊戲時,如果使用IB構建了大量的自定義的UI,那麼在由iPhone版轉向iPad版的過程中所面臨的一個重要問題就是如何從不同的nib中載入介面。在iOS5之前,所有的UIViewController在使用預設的介面載入時(init或者initWithNibName:bundle:),都會走-loadNibNamed:owner:options:。而因為我們無法拿到-loadNibNamed:owner:options的實現,因此對其過載是比較困難而且存在風險的。因此在做iPad版本的nib時,一個簡單的辦法是將所有的nib的命名方式統一,然後使用自己實現的新的類似-loadNibNamed:owner:options的方法將原方法替換掉,同時保證非iPad的裝置還走原來的loadNibNamed:owner:options方法。使用OC執行時特性可以較簡單地完成這一任務。
程式碼如下,在程式執行時呼叫+swizze,交換自己實現的loadPadNibNamed:owner:options和系統的loadNibNamed:owner:options,之後所有的loadNibNamed:owner:options訊息都將會發為loadPadNibNamed:owner:options,由自己的程式碼進行處理。
+(BOOL)swizze { Method oldMethod = class_getInstanceMethod(self, @selector(loadNibNamed:owner:options:)); if (!oldMethod) { return NO; } Method newMethod = class_getInstanceMethod(self, @selector(loadPadNibNamed:owner:options:)); if (!newMethod) { return NO; } method_exchangeImplementations(oldMethod, newMethod); return YES; }
loadPadNibNamed:owner:options的實現如下,注意在其中的loadPadNibNamed:owner:options由於之前已經進行了交換,因此實際會傳送為系統的loadNibNamed:owner:options。以此完成了對不同資源的載入。
-(NSArray *)loadPadNibNamed:(NSString *)name owner:(id)owner options:(NSDictionary *)options { NSString *newName = [name stringByReplacingOccurrencesOfString:@"@pad" withString:@""]; newName = [newName stringByAppendingFormat:@"@pad"]; //判斷是否存在 NSFileManager *fm = [NSFileManager defaultManager]; NSString* filepath = [[NSBundle mainBundle] pathForResource:newName ofType:@”nib”]; //這裡呼叫的loadPadNibNamed:owner:options:實際為為交換後的方法,即loadNibNamed:owner:options: if ([fm fileExistsAtPath:filepath]) { return [self loadPadNibNamed:newName owner:owner options:options]; } else { return [self loadPadNibNamed:name owner:owner options:options]; } }
當然這只是一個簡單的例子,而且這個功能也可以通過別的方法來實現。比如新增UIViewController的類別來過載init,但是這樣的過載會比 較危險,因為你UIViewController的實現你無法完全知道,因此可能會由於過載導致某些本來應有的init程式碼沒有覆蓋,從而出現不可預測的 錯誤。當然在上面這個例子中過載VC的init的話沒有什麼問題(因為對於VC,init的方法會被自動轉發為 loadNibNamed:owner:options,因此init方法並沒有做其他更復雜的事情,因此只要初始化VC時使用的都是init的話就沒有 問題)。但是並不能保證這樣的過載對於其他類也是可行的。因此對於實現未知的系統方法,使用上面的執行時交換方法會是一個不錯的選擇~
相關文章
- 動態修改應用icon和name
- 動態代理的實際應用
- 動態代理的原理及其應用
- 互動投影的應用都具備哪些特性
- [譯] Swift 中的動態特性Swift
- Objective-C執行時特性:Method Swizzling魔法Object
- 巧用 LLVM 特性: Objective-C Class Properties 解耦LVMObject解耦
- Flutter 動態更改應用程式啟動圖示Flutter
- Android動態更換應用圖示Android
- cglib動態代理和jdk動態代理的區別與應用CGLibJDK
- 移動應用變現AdMob入門指南(上)
- 《Effective Objective-C 2.0》- 5:用列舉來表示狀態、選項、狀態碼Object
- 使用NAS動態儲存卷建立有狀態應用
- 資料庫安全應用指南重磅釋出 安華金和不遺餘力推動"大生態"資料庫
- Spark 3.0 新特性 之 自適應查詢與分割槽動態裁剪Spark
- SAST 與 DAST:靜態和動態應用程式安全測試AST
- Electron 應用除錯指南除錯
- 動態IP代理的應用場景有哪些?
- 00024.06 反射:反射的應用2:動態建立物件反射物件
- Android動態修改應用圖示和名稱Android
- 使用 nuxi dev 啟動 Nuxt 應用程式的詳細指南UXdev
- Google :2021移動應用全球化指南(附下載)Go
- Objective-C autosynthesis(自動合成)Object
- Objective-C 執行時程式設計指南-介紹Object程式設計
- Vue 應用效能優化指南Vue優化
- Umi + qiankun 實現動態載入子應用路由路由
- php中的動態變數的一個應用PHP變數
- 安卓應用安全指南4.1.2建立/使用活動規則書安卓
- Python 語言特性:編譯+解釋、動態型別語言、動態語言Python編譯型別
- Flink ML的新特性解析與應用
- iOS應用崩潰日誌.crash報告符號化/.dYSM符號表手動解析(Objective-C)iOS應用崩潰符號Object
- 安卓應用安全指南4.1.1建立/使用活動示例程式碼安卓
- 藉助 Material You 動態配色豐富您的應用
- RiskIQ:2019年移動應用威脅態勢報告
- 「譯」有限狀態機在 CSS 動畫中的應用CSS動畫
- Jenkins實戰之動態替換Android應用圖示JenkinsAndroid
- 應用架構指南全新發布應用架構
- Google官方應用程式架構指南Go架構
- 應用遷移至 Android P 操作指南Android