動(dòng)態(tài)方法調(diào)用

在介紹動(dòng)態(tài)方法調(diào)用之前,我們先搞清楚方法調(diào)用的本質(zhì)是什么。了解runtime的朋友都知道,OC語言會(huì)在編譯期轉(zhuǎn)換成C語言,所有調(diào)用方法的代碼實(shí)際上主要會(huì)轉(zhuǎn)換成objc_msgSend函數(shù),所以O(shè)C的方法調(diào)用更準(zhǔn)確的說應(yīng)該叫發(fā)送消息。

OC代碼
/* 方法調(diào)用 */
[target doSomeThing:action];
C語言代碼:
/** 
    objc_msgSend函數(shù)的具體調(diào)用形式 
    @param id 發(fā)送消息的對(duì)象(target)的類型
    @param SEL 方法(@selector(doSomeThing:) )選擇器
    @param id 參數(shù)(action)的類型
*/
((id (*)(id,SEL,id))objc_msgSend)(target, @selector(doSomeThing:) ,action);

如果我們直接通過objc_msgSend來調(diào)用方法,那么只需要通過接口傳入方法調(diào)用者、方法選擇器、參數(shù),即可以實(shí)現(xiàn)方法的調(diào)用。那是不是就能實(shí)現(xiàn)動(dòng)態(tài)方法調(diào)用呢?
我們來實(shí)現(xiàn)這個(gè)功能:

+ (id)performTarget:(id)taget selector:(SEL)selector param:(id)param {
    ((id (*)(id,SEL,id))objc_msgSend)(taget, selector ,param);
}

這是當(dāng)只有一個(gè)參數(shù)的時(shí)候的方法,如果有兩個(gè)參數(shù),那么就要再加一個(gè)方法

+ (id)performTarget:(id)taget selector:(SEL)selector param1:(id)param1 param2:(id)param2 {
    ((id (*)(id,SEL,id,id))objc_msgSend)(taget, selector ,param1 ,param2);
}

那么假如更多個(gè)參數(shù)呢,每多一個(gè)參數(shù)都需要多加一個(gè)方法。直接調(diào)用objc_msgSend的缺點(diǎn)就顯現(xiàn)出來了,它并不能動(dòng)態(tài)的調(diào)用不同數(shù)量參數(shù)的函數(shù)。
其實(shí)OC的api就自帶了這么一個(gè)方法調(diào)用的機(jī)制:

- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

api默認(rèn)給了3個(gè)方法,不帶參數(shù)的、帶一個(gè)參數(shù)的、帶兩個(gè)參數(shù)的,如果有需要我們可以寫帶更多參數(shù)的方法。但是這不是我們想要的,我們希望不管有多少個(gè)參數(shù),都只需要調(diào)用一個(gè)方法即可實(shí)現(xiàn)。這樣做的好處是顯而易見的,比如我們可以通過后臺(tái)接口返回的數(shù)據(jù)來決定調(diào)用哪個(gè)方法,以及傳什么參數(shù)。

NSInvocation

既然直接發(fā)送消息不能實(shí)現(xiàn)動(dòng)態(tài)方法調(diào)用,那么有沒有辦法能實(shí)現(xiàn)呢。繼續(xù)從runtime尋找答案,runtime機(jī)制中除了發(fā)送消息,還有消息轉(zhuǎn)發(fā)。調(diào)用方法時(shí),如果對(duì)象找不到對(duì)應(yīng)方法名的方法,就會(huì)進(jìn)行消息轉(zhuǎn)發(fā),我們通常會(huì)在項(xiàng)目中交換系統(tǒng)的轉(zhuǎn)發(fā)方法,動(dòng)態(tài)添加新方法,以避免因?yàn)檎{(diào)用為定義的方法而產(chǎn)生的崩潰。而在消息轉(zhuǎn)發(fā)的方法中有一個(gè)NSInvocation類很可疑,我們?nèi)ス俜轿臋n看看它的介紹。

NSInvocation的官方文檔

大致的意思是

NSInvocation使用于在對(duì)象和應(yīng)用程序之間存儲(chǔ)和轉(zhuǎn)發(fā)消息,一個(gè)NSInvocation對(duì)象包含了Object-C的所有元素:一個(gè)target、一個(gè)方法選擇器、參數(shù)和返回值

NSInvocation對(duì)象可以被反復(fù)的分配給不同的target,參數(shù)可以在NSInvocation對(duì)象執(zhí)行之前修改,選擇器可以在不改變方法簽名的情況下修改成不同的值,這使得invocation非常靈活并且適合處理有多個(gè)參數(shù)的情況。

NSInvocation不支持調(diào)用其他的類方法來創(chuàng)建對(duì)象,不管這個(gè)方法是有一個(gè)或者多個(gè)參數(shù)。只能使用invocationWithMethodSignature:方法來創(chuàng)建一個(gè)NSInvocation對(duì)象,而不能使用alloc init方法創(chuàng)建對(duì)象。

invocation默認(rèn)不對(duì)內(nèi)部的參數(shù)進(jìn)項(xiàng)強(qiáng)引用,如果這些參數(shù)在你創(chuàng)建或者使用invocation對(duì)象的時(shí)候會(huì)消失,你應(yīng)該手動(dòng)的去強(qiáng)引用這些參數(shù)或者調(diào)用invocation的retainArguments方法讓invocation來強(qiáng)引用這些參數(shù)。

從官方文檔我們可以了解到,NSInvocation非常適用于調(diào)用多個(gè)參數(shù)的情況,具體代碼如下:

/** 
    多參數(shù)的方法調(diào)用
    @param target 發(fā)送消息的對(duì)象
    @param action 方法名
    @param arguments 參數(shù)數(shù)組
*/
+ (id)performTarget:(id)target action:(NSString *)action arguments:(NSArray *)arguments {
    SEL sel = NSSelectorFromString(action);
    if (!sel || ![target respondsToSelector:sel]) {
        return nil;
    }

    NSMethodSignature *signature = [target methodSignatureForSelector:sel];
    NSUInteger number = signature.numberOfArguments;
    if (number < 2 || number > arguments.count + 2) {
        return nil;
    }
    /* NSInvocation不能通過alloc初始化,只能使用invocationWithMethodSignature:方法創(chuàng)建NSInvocation對(duì)象 */
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    invocation.target = target;
    invocation.selector = sel;
    /* invocation的第一個(gè)參數(shù)是target,第二個(gè)參數(shù)是selector,所以方法的參數(shù)的下標(biāo)要從2開始 */
    for (int i = 0; i < arguments.count; i++) {
        int index = i + 2;
        id object = arguments[i];
        void * argument = &object;
        const char *type = [signature getArgumentTypeAtIndex:index];
        /* 因?yàn)閍rgument傳的是地址,所以對(duì)非對(duì)象類型進(jìn)行轉(zhuǎn)換,這里以NSInteger為例 */
        if (strcmp(type, @encode(NSInteger)) == 0) {
            NSInteger num = [object unsignedIntegerValue];
            argument = &num;
        }
        [invocation setArgument:argument atIndex:index];
    }
    /* 調(diào)用方法 */
    [invocation invoke];
    void *returnValue;
    /* 獲取返回值 */
    [invocation getReturnValue:&returnValue];
    id result = (__bridge id)returnValue;
    return result;
}

這樣不管參數(shù)有多少都可以通過這一個(gè)方法進(jìn)行調(diào)用了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容