<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
Runtime 是使用 C 和組合實現的執行時程式碼庫,Objective-C 中有很多語言特性都是通過它來實現。瞭解 Runtime 開發可以幫助我們更靈活的使用 Objective-C 這門語言,我們可以將程式功能推遲到執行時再去決定怎麼做,還可以利用 Runtime 來解決專案開發中的一些設計和技術問題,使開發過程更加具有靈活性。
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 方式來擴充套件類的功能。
在 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 需要注意下面幾個問題
我們可以通過 Runtime 特性來獲得類的所有屬性名稱和型別,然後再通過 KVC 將 JSON 中的值填充給該類的物件。還可以在程式執行時為類新增方法或替換方法從而使物件能夠更靈活的根據需要來選擇實現方法。總之 Runtime 庫就象一堆積木,只要發揮想象力便能實現各種各樣的功能,但前提是你需要了解它。
以上就是Objective-C Runtime 開發範例詳解的詳細內容,更多關於Objective-C Runtime 開發的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45