您好,登錄后才能下訂單哦!
這篇文章主要介紹“IOS開發Objective-C Runtime如何使用”,在日常操作中,相信很多人在IOS開發Objective-C Runtime如何使用問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”IOS開發Objective-C Runtime如何使用”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
Runtime 是使用 C 和匯編實現的運行時代碼庫,Objective-C 中有很多語言特性都是通過它來實現。了解 Runtime 開發可以幫助我們更靈活的使用 Objective-C 這門語言,我們可以將程序功能推遲到運行時再去決定怎么做,還可以利用 Runtime 來解決項目開發中的一些設計和技術問題,使開發過程更加具有靈活性。
self:類的隱藏參數變量,指向當前調用方法的對象
super:是編譯器的標示符,通過 super 調用方法會被翻譯成 objc_msgSendSuper(self, _cmd,…)
SEL:以方法名為內容的 C 字符串
IMP:指向方法實現的函數指針
id:指向類對象或實例對象的指針
isa:為 id 對象所屬類型 (objc_class),Objc 中的繼承就是通過 isa 指針找到 objc_class,然后再通過 super_class 去找對應的父類
metaclass:在 Objc 中,類本身也是對象,實例對象的 isa 指向它所屬的類,而類對象的 isa 指向元類 (metaclass),元類的 isa 直接指向根元類,根元類的isa指向它自己,它們之間的關系如下圖所示。
Objective-C 對于調用對象的某個方法這種行為叫做給對象發送消息,實際上就是沿著它的 isa 指針去查找真正的函數地址。下面我們來了解一下這個過程:
我們寫一個給對象發送消息的代碼
[array insertObject:obj atIndex:5];
編譯器首先會將上面代碼翻譯成這種樣子
objc_msgSend(array, @selector(insertObject:atIndex:), obj, 5);
系統在運行時會通過 array 對象的 isa 指針找到對應的 class(如果是給類發消息,則找到的是metaclass),然后在 class 的 cache 方法列表中用 SEL 去找對應 method,如果找不到便去 class 的方法列表中去找,如果在方法列表中也找不對對應 method 時,便沿著繼承體系繼續向上查找,找到后將 method 放入 cache,以便下次能快速定位,然后再去執行 method 的 IMP,找不到時系統便報錯:unrecognized selector sent to insertObject:atIndex:
Runtime 提供了三種方法避免因為找不到方法而崩潰
當找不到方法實現時,Runtime 會先發送 +resolveInstanceMethod: 或 +resolveClassMethod: 消息,我們可以重寫它然后為對象指定一個處理方法。
void dynamicXXXMethod(id obj, SEL _cmd) { NSLog(@"ok..."); } + (BOOL)resolveInstanceMethod:(SEL)aSEL { if(aSEL == @selector(xxx:)) { class_addMethod([self class], aSEL, (IMP)dynamicXXXMethod, "v@:"); return YES; } return [super resolveInstanceMethod]; }
class_addMethod 方法的最后一個參數用來指定所添加方法的參數及返回值,叫 Type Encodings。
如果 resolve 方法返回 NO,Runtime 會發送 -forwardingTargetForSelector: 消息,允許我們將消息轉發給能處理它的其它對象。
- (id)forwardingTargetForSelector:(SEL)aSelector { if(aSelector == @selector(xxx:)){ return otherObject; } return [super forwardingTargetForSelector:aSelector]; }
當 -forwardingTargetForSelector: 返回 nil 時,Runtime 會發送 -methodSignatureForSelector: 和 -forwardInvocation: 消息。我們可以選擇忽略消息、拋出異常、將消息轉由當前對象或其它對象的任意消息來處理。
//根據 SEL 生成 NSInvocation 對象,然后再由 -forwardInvocation: 方法進行轉發。 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { NSMethodSignature *signature = [super methodSignatureForSelector:aSelector]; if (!signature) { signature = [otherObject instanceMethodSignatureForSelector:aSelector]; } return signature; } - (void)forwardInvocation:(NSInvocation *)invocation { SEL sel = invocation.selector; if([otherObject respondsToSelector:sel]) { [invocation invokeWithTarget:otherObject]; // 轉發消息 } else { [self doesNotRecognizeSelector:sel]; // 拋出異常 } }
當我們為對象添加觀察者后,Runtime 會在運行時創建這個對象所在類的子類,并且將該對象的 isa 指針指向這個子類,然后重寫監聽屬性的 set 方法并在方法中調用 -willChangeValueForKey: 和 -didChangeValueForKey: 來通知觀察者,所以如果直接修改實例變量便不會觸發監聽方法。當移除觀察者后,Runtime 便會將這個子類刪除。
所以 isa 指針并不總是指向實例對象所屬的類,也有可能指向一個中間類,所以不能依靠它來確定類型,而是應該用 class 方法來確定實例對象的類。
在 Category 中可以為類添加實例方法或類方法,但是不支持添加實例變量,所以即使我們在 Category 中為類添加了 property,也不能直接使用它,Runtime 可以解決這個問題,我們只需要定義一個指針,然后通過 objc_setAssociatedObject 方法將指針與對象進行關聯并指定內存管理方式,數據以 KeyValue 的形式存儲在一個 HashMap 里。
Objc 中的類和對象都是結構體,Category 也是這樣,定義的方法和屬性在結構體中的存儲,并在運行時按倒序添加到主類中(添加的方法會放在方法列表的上面),所以如果添加的方法與原類中的一樣,那么在調用此方法時,優先找到的便是我們添加的這個方法。如果有多個 Category 添加同樣名稱的方法,那么這些方法在方法列表中的順序取決于他們的編譯順序,也就是這些 Category 文件在 Compile Sources 中的順序。
@interface NSObject (JC) @property (nonatomic, copy) NSString *ID; @end @implementation NSObject (JC) static const void *IDKey; - (NSString *)ID { return objc_getAssociatedObject(self, &IDKey); } - (void)setID:(NSString *)ID { objc_setAssociatedObject(self, &IDKey, ID, OBJC_ASSOCIATION_COPY_NONATOMIC); } @end
我們可以通過繼承、Category、AOP 方式來擴展類的功能。
繼承比較適合在設計底層代碼架構時使用,不適當的使用會讓代碼看起來很啰嗦,并且增加維護難度。
Category 適合為現有類添加方法。
當需要修改現有類的方法并且拿不到源碼時,繼承和 AOP 都能解決問題,但是用 AOP 來解決代碼耦合度更低。其實就算能拿到源碼,往往直接去改源碼也不是個好辦法。
在 Objective-C 中,可以通過 Method Swizzling 技術來實現 AOP,下面我們通過交換兩個方法的實現代碼來向已存在的方法中添加其它功能。
#import <objc/runtime.h> @implementation UIViewController (Tracking) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class aClass = [self class]; SEL originalSelector = @selector(viewWillAppear:); SEL swizzledSelector = @selector(swizzled_viewWillAppear:); Method originalMethod = class_getInstanceMethod(aClass, originalSelector); Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector); // 如果要對類方法進行交換,使用下面注釋的代碼 // Class aClass = object_getClass((id)self); // // Method originalMethod = class_getClassMethod(aClass, originalSelector); // Method swizzledMethod = class_getClassMethod(aClass, swizzledSelector); // 交換兩個方法的實現 // 防止 aClass 不存在 originalSelector,所以添加一下試試,但指向地址為新方法地址 BOOL didAddMethod = class_addMethod(aClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { // 添加成功,說明 aClass 不存在 originalSelector,所以替換 swizzledSelector 的 IMP 為 originalMethod,實質上它們都指向 swizzledMethod class_replaceMethod(aClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { // 添加失敗,說明 aClass 存在 originalSelector,直接交換 method_exchangeImplementations(originalMethod, swizzledMethod); } }); } #pragma mark - Method Swizzling // 由于方法實現已經被交換,所以系統在調用 viewWillAppear: 時,實際上會調用 swizzled_viewWillAppear: - (void)swizzled_viewWillAppear:(BOOL)animated { // 下面代碼表面上看起來會引起遞歸調用,由于函數實現已經被交換,實際上會調用 viewWillAppear: [self swizzled_viewWillAppear:animated]; // 在原有基礎上添加其它功能(寫日志等) } @end
使用 Method Swizzling 需要注意下面幾個問題
需要在 +load 方法中執行 Method Swizzling,+initialize 方法有可能不會被調用
避免父類與子類同時 hook 父類的某方法,避免不了時至少要保證不在 +load 方法中執行 super.load(),否則父類中的 +load 方法會被執行兩次
需要在 dispatch_once 中執行,避免因多線程等問題倒致的偶數次交換后失效的問題
如果你用了 swizzled_viewWillAppear 作為方法名,那么如果你引用的第三方 SDK 中也用了這個方法名來做方法交換,那會造成方法的遞歸調用,所以你最好換一個不太會被重復使用的方法名,例如 mx_swizzled_viewWillAppear
即便使用 mx_swizzled_viewWillAppear 盡量避免了與第三方庫或自己項目中別的地方對 viewWillAppear 交換倒致的遞歸調用問題,仍然會存在調用順序問題,解決辦法就是在 Build Phases 中調整類文件的順序
我們可以通過 Runtime 特性來獲得類的所有屬性名稱和類型,然后再通過 KVC 將 JSON 中的值填充給該類的對象。還可以在程序運行時為類添加方法或替換方法從而使對象能夠更靈活的根據需要來選擇實現方法。總之 Runtime 庫就象一堆積木,只要發揮想象力便能實現各種各樣的功能,但前提是你需要了解它。
到此,關于“IOS開發Objective-C Runtime如何使用”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。