使用runtime給類動態添加方法並調用,runtime動態

來源:互聯網
上載者:User

使用runtime給類動態添加方法並調用,runtime動態

上手開發 iOS 一段時間後,我發現並不能只著眼於完成需求,利用閑暇之餘多研究其他的開發技巧,才能在有限時間內提升自己水平。當然,“其他開發技巧”這個命題對於任何一個開發領域都感覺不找邊際,而對於我來說,嘗試接觸 objc/runtime 不失為是開始深入探索 iOS 開發的第一步。

剛瞭解 runtime 當然要從比較簡單的 api 開始,今天就羅列整理一下 class_addMethod 的相關點:

首先從文檔開始。

/**  * Adds a new method to a class with a given name and implementation. *  * @param cls The class to which to add a method. * @param name A selector that specifies the name of the method being added. * @param imp A function which is the implementation of the new method. The function must take at least two arguments—self and _cmd. * @param types An array of characters that describe the types of the arguments to the method.  *  * @return YES if the method was added successfully, otherwise NO  *  (for example, the class already contains a method implementation with that name). * * @note class_addMethod will add an override of a superclass's implementation,  *  but will not replace an existing implementation in this class.  *  To change an existing implementation, use method_setImplementation. */OBJC_EXPORT BOOL class_addMethod(Class cls, SEL name, IMP imp,                                  const char *types)      __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);

大意翻譯一下,這個方法的作用是,給類添加一個新的方法和該方法的具體實現。分析一下這個方法需要的參數:

Class cls

cls 參數表示需要添加新方法的類。

SEL name

name 參數表示 selector 的方法名稱,可以根據喜好自己進行命名。

IMP imp

imp 即 implementation ,表示由編譯器產生的、指向實現方法的指標。也就是說,這個指標指向的方法就是我們要添加的方法。

const char *types

最後一個參數 *types 表示我們要添加的方法的傳回值和參數。

 

簡要介紹了 class_addMethod 中所需要的參數以及作用之後,我們就可以開始利用這個方法進行添加我們所需要的方法啦!在使用之前,我們首先要明確 Objective-C 作為一種動態語言,它會將部分代碼放置在運行時的過程中執行,而不是編譯時間,所以在執行代碼時,不僅僅需要的是編譯器,也同時需要一個運行時環境(Runtime),為了滿足一些需求,蘋果開源了 Runtime Source 並提供了開放的 api 供開發人員使用。

其次,我們需要知道在什麼情況下需要調用 class_addMethod 這個方法。當項目中,需要繼承某一個類(subclass),但是父類中並沒有提供我需要的調用方法,而我又不清楚父類中某些方法的具體實現;或者,我需要為這個類寫一個分類(category),在這個分類中,我可能需要替換/新增某個方法(注意:不推薦在分類中重寫方法,而且也無法通過 super 來擷取所謂父類的方法)。大致在這兩種情況下,我們可以通過 class_addMethod 來實現我們想要的效果。 

好了,說了這麼多那麼到底應該如何調用呢?如果不清楚使用方法,那麼看說明書就是最好的方法。在 Apple 提供的文檔中就有詳細的使用方法(Objective-C Runtime Programming Guide - Dynamic Method Resolution),以下內容就以 myCar 這個類來詳細說明一下具體的使用規則:

首先,既然要給某個類添加我們的方法,就應該繼承或者給這個類寫一個分類,這裡我建立一個名為「myCar」的類,作為「Car」類的分類。

#import "Car+myCar.h"@implementation Car (myCar)@end

我們知道,在 Objective-C 中,正常的調用方法是通過訊息機制(message)來實現的,那麼如果類中沒有找到發送的訊息方法,系統就會進入找不到該方法的處理流程中,如果在這個流程中,我們加入我們所需要的新方法,就能實現運行過程中的動態添加了。這個流程或者說機制,就是 Objective-C 的 Message Forwarding 

這個機制中所涉及的方法主要有兩個:

+ (BOOL)resolveInstanceMethod:(SEL)sel+ (BOOL)resolveClassMethod:(SEL)sel

 兩個方法的唯一區別在於需要添加的是靜態方法還是執行個體方法。我們就拿前者來說,既然要添加方法,我們就在「myCar」類中實現它,代碼如下:

#import "Car+myCar.h"void startEngine(id self, SEL _cmd) {    NSLog(@"my car starts the engine");}@implementation Car (myCar)@end

至此,我們實現了我們要添加的 startEngine 這個方法。這是一個 C 語言的函數,它至少包含了 self 和 _cmd 兩個參數(self 代表著函數本身,而 _cmd 則是一個 SEL 資料體,包含了具體的方法地址)。如果要在這個方法中新增參數呢?見如下代碼:

#import "Car+myCar.h"void startEngine(id self, SEL _cmd, NSString *brand) {    NSLog(@"my %@ car starts the engine", brand);}@implementation Car (myCar)@end

只要在那兩個必須的參數之後添加所需要的參數和類型就可以了,傳回值同樣道理,只要把方法名之前的 void 修改成我們想要的傳回型別就可以,這裡我們不需要傳回值。

接著,我們重載 resolveInstanceMethod: 這個函數:

#import "Car+myCar.h"#import <objc/runtime.h>void startEngine(id self, SEL _cmd, NSString *brand) {    NSLog(@"my %@ car starts the engine", brand);}@implementation Car (myCar)+ (BOOL)resolveInstanceMethod:(SEL)sel {    if (sel == @selector(drive)) {        class_addMethod([self class], sel, (IMP)startEngine, "v@:@");        return YES;    }    return [super resolveInstanceMethod:sel];}@end

 解釋一下,這個函數在 runtime 環境下,如果沒有找到該方法的實現的話就會執行。第一行判斷的是傳入的 SEL 名稱是否匹配,接著調用 class_addMethod 方法,傳入相應的參數。其中第三個參數傳入的是我們添加的 C 語言函數的實現,也就是說,第三個參數的名稱要和添加的具體函數名稱一致。第四個參數指的是函數的傳回值以及參數內容。

至於該類方法的傳回值,在我測試的時候,無論這個 BOOL 值是多少,並不會影響我們的執行目標,一般返回 YES 即可。

如果覺得用 C 語言風格寫新函數比較不適應,那麼可以改寫成以下的代碼:

@implementation Car (myCar)+ (BOOL)resolveInstanceMethod:(SEL)sel {    if (sel == @selector(drive)) {        class_addMethod([self class], sel, class_getMethodImplementation(self, @selector(startEngine:)), "s@:@");        return YES;    }    return [super resolveInstanceMethod:sel];}- (void)startEngine:(NSString *)brand {    NSLog(@"my %@ car starts the engine", brand);}@end

其中 class_getMethodImplementation 意思就是擷取 SEL 的具體實現的指標。

然後建立一個新的類「DynamicSelector」,在這個新類中我們實現對「Car」的動態添加方法。

#import "DynamicSelector.h"#import "Car+myCar.h"@implementation DynamicSelector- (void)dynamicAddMethod {    Car *c = [[Car alloc] init];    [c performSelector:@selector(drive) withObject:@"bmw"];}@end

注意,在這裡就不能使用 [self method:] 進行調用了,因為我們添加的方法是在運行時才執行,而編譯器只負責編譯時間的方法檢索,一旦對一個對象沒有檢索到它的 drive 方法,就會報錯,所以這裡我們使用 performSelector:withObject: 來進行調用,儲存,運行。

2016-08-26 10:50:17.207 objc-runtime[76618:3031897] my bmw car starts the engineProgram ended with exit code: 0

列印結果符合我們期望實現的目標。如果需要傳回值,方法類似。

 

項目已上傳至 https://github.com/zhangqifan/class_addMethod 有需要的可以 clone 源碼,如需指正請 push issue。

 

本人原創,轉載請註明連結。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.