iOS Runntime 動(dòng)態(tài)添加類方法并調(diào)用-class_addMethod
上手開發(fā) iOS 一段時(shí)間后,我發(fā)現(xiàn)并不能只著眼于完成需求,利用閑暇之余多研究其他的開發(fā)技巧,才能在有限時(shí)間內(nèi)提升自己水平。當(dāng)然,“其他開發(fā)技巧”這個(gè)命題對于任何一個(gè)開發(fā)領(lǐng)域都感覺不找邊際,而對于我來說,嘗試接觸 objc/runtime 不失為是開始深入探索 iOS 開發(fā)的第一步。
剛了解 runtime 當(dāng)然要從比較簡單的 api 開始,今天就羅列整理一下 class_addMethod 的相關(guān)點(diǎn):
首先從文檔開始。
/** * 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);
大意翻譯一下,這個(gè)方法的作用是,給類添加一個(gè)新的方法和該方法的具體實(shí)現(xiàn)。分析一下這個(gè)方法需要的參數(shù):
Class cls
cls 參數(shù)表示需要添加新方法的類。
SEL name
name 參數(shù)表示 selector 的方法名稱,可以根據(jù)喜好自己進(jìn)行命名。
IMP imp
imp 即 implementation ,表示由編譯器生成的、指向?qū)崿F(xiàn)方法的指針。也就是說,這個(gè)指針指向的方法就是我們要添加的方法。
const char *types
最后一個(gè)參數(shù) *types 表示我們要添加的方法的返回值和參數(shù)。
簡要介紹了 class_addMethod 中所需要的參數(shù)以及作用之后,我們就可以開始利用這個(gè)方法進(jìn)行添加我們所需要的方法啦!在使用之前,我們首先要明確 Objective-C 作為一種動(dòng)態(tài)語言,它會(huì)將部分代碼放置在運(yùn)行時(shí)的過程中執(zhí)行,而不是編譯時(shí),所以在執(zhí)行代碼時(shí),不僅僅需要的是編譯器,也同時(shí)需要一個(gè)運(yùn)行時(shí)環(huán)境(Runtime),為了滿足一些需求,蘋果開源了 Runtime Source 并提供了開放的 api 供開發(fā)者使用。
其次,我們需要知道在什么情況下需要調(diào)用 class_addMethod 這個(gè)方法。當(dāng)項(xiàng)目中,需要繼承某一個(gè)類(subclass),但是父類中并沒有提供我需要的調(diào)用方法,而我又不清楚父類中某些方法的具體實(shí)現(xiàn);或者,我需要為這個(gè)類寫一個(gè)分類(category),在這個(gè)分類中,我可能需要替換/新增某個(gè)方法(注意:不推薦在分類中重寫方法,而且也無法通過 super 來獲取所謂父類的方法)。大致在這兩種情況下,我們可以通過 class_addMethod 來實(shí)現(xiàn)我們想要的效果。
好了,說了這么多那么到底應(yīng)該如何調(diào)用呢?如果不清楚使用方法,那么看說明書就是最好的方法。在 Apple 提供的文檔中就有詳細(xì)的使用方法(Objective-C Runtime Programming Guide - Dynamic Method Resolution),以下內(nèi)容就以 myCar 這個(gè)類來詳細(xì)說明一下具體的使用規(guī)則:
首先,既然要給某個(gè)類添加我們的方法,就應(yīng)該繼承或者給這個(gè)類寫一個(gè)分類,這里我新建一個(gè)名為「myCar」的類,作為「Car」類的分類。
#import "Car+myCar.h" @implementation Car (myCar) @end
我們知道,在 Objective-C 中,正常的調(diào)用方法是通過消息機(jī)制(message)來實(shí)現(xiàn)的,那么如果類中沒有找到發(fā)送的消息方法,系統(tǒng)就會(huì)進(jìn)入找不到該方法的處理流程中,如果在這個(gè)流程中,我們加入我們所需要的新方法,就能實(shí)現(xiàn)運(yùn)行過程中的動(dòng)態(tài)添加了。這個(gè)流程或者說機(jī)制,就是 Objective-C 的 Message Forwarding
這個(gè)機(jī)制中所涉及的方法主要有兩個(gè):
+ (BOOL)resolveInstanceMethod:(SEL)sel + (BOOL)resolveClassMethod:(SEL)sel
兩個(gè)方法的唯一區(qū)別在于需要添加的是靜態(tài)方法還是實(shí)例方法。我們就拿前者來說,既然要添加方法,我們就在「myCar」類中實(shí)現(xiàn)它,代碼如下:
#import "Car+myCar.h" void startEngine(id self, SEL _cmd) { NSLog(@"my car starts the engine"); } @implementation Car (myCar) @end
至此,我們實(shí)現(xiàn)了我們要添加的 startEngine 這個(gè)方法。這是一個(gè) C 語言的函數(shù),它至少包含了 self 和 _cmd 兩個(gè)參數(shù)(self 代表著函數(shù)本身,而 _cmd 則是一個(gè) SEL 數(shù)據(jù)體,包含了具體的方法地址)。如果要在這個(gè)方法中新增參數(shù)呢?見如下代碼:
#import "Car+myCar.h" void startEngine(id self, SEL _cmd, NSString *brand) { NSLog(@"my %@ car starts the engine", brand); } @implementation Car (myCar) @end
只要在那兩個(gè)必須的參數(shù)之后添加所需要的參數(shù)和類型就可以了,返回值同樣道理,只要把方法名之前的 void 修改成我們想要的返回類型就可以,這里我們不需要返回值。
接著,我們重載 resolveInstanceMethod: 這個(gè)函數(shù):
#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
解釋一下,這個(gè)函數(shù)在 runtime 環(huán)境下,如果沒有找到該方法的實(shí)現(xiàn)的話就會(huì)執(zhí)行。第一行判斷的是傳入的 SEL 名稱是否匹配,接著調(diào)用 class_addMethod 方法,傳入相應(yīng)的參數(shù)。其中第三個(gè)參數(shù)傳入的是我們添加的 C 語言函數(shù)的實(shí)現(xiàn),也就是說,第三個(gè)參數(shù)的名稱要和添加的具體函數(shù)名稱一致。第四個(gè)參數(shù)指的是函數(shù)的返回值以及參數(shù)內(nèi)容。
至于該類方法的返回值,在我測試的時(shí)候,無論這個(gè) BOOL 值是多少,并不會(huì)影響我們的執(zhí)行目標(biāo),一般返回 YES 即可。
如果覺得用 C 語言風(fēng)格寫新函數(shù)比較不適應(yīng),那么可以改寫成以下的代碼:
@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 的具體實(shí)現(xiàn)的指針。
然后創(chuàng)建一個(gè)新的類「DynamicSelector」,在這個(gè)新類中我們實(shí)現(xiàn)對「Car」的動(dòng)態(tài)添加方法。
#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:] 進(jìn)行調(diào)用了,因?yàn)槲覀兲砑拥姆椒ㄊ窃谶\(yùn)行時(shí)才執(zhí)行,而編譯器只負(fù)責(zé)編譯時(shí)的方法檢索,一旦對一個(gè)對象沒有檢索到它的 drive 方法,就會(huì)報(bào)錯(cuò),所以這里我們使用 performSelector:withObject: 來進(jìn)行調(diào)用,保存,運(yùn)行。
2016-08-26 10:50:17.207 objc-runtime[76618:3031897] my bmw car starts the engine Program ended with exit code: 0
打印結(jié)果符合我們期望實(shí)現(xiàn)的目標(biāo)。如果需要返回值,方法類似。
以上所述是小編給大家介紹的iOS Runntime 動(dòng)態(tài)添加類方法并調(diào)用-class_addMethod,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
- iOS App初次啟動(dòng)時(shí)的用戶引導(dǎo)頁制作實(shí)例分享
- iOS 引導(dǎo)頁的鏤空效果實(shí)例
- iOS開發(fā)的UI制作中動(dòng)態(tài)和靜態(tài)單元格的基本使用教程
- ios動(dòng)態(tài)設(shè)置lbl文字標(biāo)簽的高度
- iOS實(shí)現(xiàn)動(dòng)態(tài)的開屏廣告示例代碼
- iOS開發(fā)中ViewController的頁面跳轉(zhuǎn)和彈出模態(tài)
- iOS開發(fā)中WebView的基本使用方法簡介
- IOS獲取各種文件目錄路徑的方法
- iOS毛玻璃效果的實(shí)現(xiàn)及圖片模糊效果的三種方法
- iOS實(shí)現(xiàn)動(dòng)態(tài)元素的引導(dǎo)圖效果
相關(guān)文章
基于iOS Realm數(shù)據(jù)庫的使用實(shí)例詳解
下面小編就為大家分享一篇基于iOS Realm數(shù)據(jù)庫的使用實(shí)例詳解,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-01-01CAMediaTiming ( 時(shí)間協(xié)議)詳解及實(shí)例代碼
這篇文章主要介紹了CAMediaTiming / 時(shí)間協(xié)議詳解及實(shí)例代碼的相關(guān)資料,這里附有實(shí)例代碼,幫助大家學(xué)習(xí)參考,需要的朋友可以參考下2016-12-12iOS實(shí)現(xiàn)逐幀動(dòng)畫做loading視圖
這篇文章主要為大家詳細(xì)介紹了iOS實(shí)現(xiàn)逐幀動(dòng)畫做loading視圖,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-05-05Objective-C中類和方法的定義以及協(xié)議的使用
這篇文章主要介紹了Objective-C中類和方法的定義以及協(xié)議的使用,配合Mac下的Xcode IDE進(jìn)行講解,需要的朋友可以參考下2016-01-01iOS開發(fā)之UIMenuController使用示例詳解
這篇文章主要為大家介紹了iOS開發(fā)之UIMenuController使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07iOS實(shí)現(xiàn)MJRefresh下拉刷新(上拉加載)使用詳解
本篇文章主要介紹了iOS實(shí)現(xiàn)MJRefresh下拉刷新(上拉加載)使用詳解,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-01-01