深入探索NSMethodSignature和NSInvocation的使用

NSMethodSignature

NSMethodSignature主要是方法的返回值和參數(shù)的類(lèi)型信息的記錄。

方法簽名由方法返回類(lèi)型的一個(gè)或多個(gè)字符組成,后跟隱式參數(shù)self_cmd的字符串編碼,還有零個(gè)或多個(gè)顯式參數(shù)。可以使用methodReturnTypemethodReturnLength屬性確定字符串編碼和返回類(lèi)型的長(zhǎng)度。numberOfArguments屬性獲取方法參數(shù)個(gè)數(shù)。可以使用getArgumentTypeAtIndex:根據(jù)索引獲取方法參數(shù)。

例如,NSString實(shí)例方法containsString:有一個(gè)帶有以下參數(shù)的方法簽名:
@encode(BOOL) (c)為返回類(lèi)型
@encode(id) (@)為接收者(self)
@encode(SEL) (:)為選擇器(_cmd)
@encode(NSString *) (@)為第一個(gè)顯式參數(shù)

NSInvocation

NSInvocation對(duì)象用于在對(duì)象之間存儲(chǔ)和轉(zhuǎn)發(fā)消息。NSInvocation對(duì)象包含一個(gè)Objective-C消息的所有元素:目標(biāo)、選擇器、參數(shù)和返回值。每個(gè)元素都可以直接設(shè)置,當(dāng)NSInvocation對(duì)象被分派時(shí),返回值會(huì)自動(dòng)設(shè)置。

一個(gè)NSInvocation對(duì)象可以被重復(fù)地分派到不同的目標(biāo);它的參數(shù)可以修改之間的調(diào)度不同的結(jié)果;甚至它的選擇器也可以更改為具有相同方法簽名(參數(shù)和返回類(lèi)型)的另一個(gè)選擇器。這種靈活性使得NSInvocation在使用許多參數(shù)和變量重復(fù)消息時(shí)非常有用;在每次將NSInvocation對(duì)象發(fā)送到一個(gè)新目標(biāo)之前,你可以根據(jù)需要修改它,而不是為每個(gè)消息重新輸入稍微不同的表達(dá)式。

NSInvocation不支持使用可變數(shù)量的參數(shù)或聯(lián)合參數(shù)的方法調(diào)用。創(chuàng)建NSInvocation對(duì)象應(yīng)該使用invocationWithMethodSignature:類(lèi)方法來(lái)創(chuàng)建,不應(yīng)該使用allocinit創(chuàng)建。

默認(rèn)情況下,該類(lèi)不保留包含的調(diào)用的參數(shù)。如果那些對(duì)象可能在你創(chuàng)建NSInvocation實(shí)例的時(shí)間和你使用它的時(shí)間之間釋放,你應(yīng)該顯式地保留你自己的對(duì)象或調(diào)用retainArguments方法來(lái)讓調(diào)用對(duì)象保留它們自己。

注意:
NSInvocation遵循NSCoding協(xié)議,但只支持NSPortCoder編碼。NSInvocation不支持歸檔。

使用

  1. 獲取對(duì)象方法簽名。
NSMethodSignature *signature = [object methodSignatureForSelector:selector];
  1. 根據(jù)方法簽名創(chuàng)建NSInvocation對(duì)象。
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
  1. 設(shè)置執(zhí)行方法的對(duì)象和方法選擇器。
invocation.target = object;
invocation.selector = selector;
  1. 設(shè)置方法參數(shù)。由于前兩個(gè)參數(shù)為隱式self和_cmd,所以需要從第三個(gè)參數(shù)開(kāi)始傳入顯式參數(shù)。

入?yún)⑿枰鶕?jù)參數(shù)的類(lèi)型進(jìn)行轉(zhuǎn)換后再設(shè)置給invocation對(duì)象。如果不正確根據(jù)類(lèi)型編碼設(shè)置參數(shù),會(huì)導(dǎo)致在方法中無(wú)法獲取到正確的值。比如傳入BOOL值@(YES),期望接收到的參數(shù)為YES,實(shí)際為NO(參數(shù)默認(rèn)值)。

NSUInteger paramCount = MIN(signature.numberOfArguments - 2, params.count);
for (int i = 0; i < paramCount; i++) {
    id argument = params[i];
    //獲取參數(shù)類(lèi)型編碼。如果不正確根據(jù)類(lèi)型編碼設(shè)置參數(shù),會(huì)導(dǎo)致在方法中無(wú)法獲取到正確的值。比如傳入BOOL值@(YES),期望接收到的參數(shù)為YES,實(shí)際為NO(參數(shù)默認(rèn)值)
    const char *argumentType = [signature getArgumentTypeAtIndex:i + 2];
    if ([argument isKindOfClass:[NSNumber class]]) {
        if (!strcmp(argumentType, @encode(BOOL))) {
            BOOL arg = [argument boolValue];
            [invocation setArgument:&arg atIndex:i + 2];
        }
        else if (!strcmp(argumentType, @encode(int))) {
            int arg = [argument intValue];
            [invocation setArgument:&arg atIndex:i + 2];
        }
        ...
    }
    else {
        [invocation setArgument:&argument atIndex:i + 2];
    }
}

invocation設(shè)置的參數(shù)可以比方法的入?yún)€(gè)數(shù)少,未被賦值的參數(shù)變量為默認(rèn)值。
比如:id類(lèi)型參數(shù)默認(rèn)值為nil,BOOL類(lèi)型參數(shù)默認(rèn)為NO等等。

  1. 引用參數(shù)并執(zhí)行方法。invocation默認(rèn)不會(huì)對(duì)入?yún)⑦M(jìn)行引用,為了防止在方法執(zhí)行完成之前參數(shù)被釋放,需要手動(dòng)持有一下參數(shù)。
//引用參數(shù),防止在方法執(zhí)行完成之前參數(shù)釋放
[invocation retainArguments];
//執(zhí)行方法
[invocation invoke];
  1. 獲取返回值并根據(jù)返回值類(lèi)型進(jìn)行類(lèi)型轉(zhuǎn)換。因?yàn)榉祷氐氖莄的數(shù)據(jù)類(lèi)型void *,所以需要將返回的類(lèi)型轉(zhuǎn)換成對(duì)應(yīng)的OC對(duì)象。
    更多編碼請(qǐng)參見(jiàn)Objective-C運(yùn)行時(shí)編程指南中的類(lèi)型編碼
//判斷是否有返回值
if (signature.methodReturnLength) {
    //獲取返回值類(lèi)型
    const char *returnType = signature.methodReturnType;
    //返回值類(lèi)型轉(zhuǎn)換
    if (!strcmp(returnType, @encode(void))) {//無(wú)返回值
        return nil;
    }
    else if (!strcmp(returnType, @encode(id))) {//對(duì)象類(lèi)型
        void *returnValue;
        [invocation getReturnValue:&returnValue];
        return (__bridge id)returnValue;
    }
    else {//基本數(shù)據(jù)類(lèi)型
        void *returnValue = (void *)malloc(signature.methodReturnLength);
        [invocation getReturnValue:returnValue];
        
        id result = nil;
        //根據(jù)類(lèi)型轉(zhuǎn)成NSNumber
        if (!strcmp(returnType, @encode(BOOL))) {
            result = [NSNumber numberWithBool:*((BOOL *)returnValue)];
        }
        else if (!strcmp(returnType, @encode(int))) {
            result = [NSNumber numberWithInt:*((int *)returnValue)];
        }
        else if (!strcmp(returnType, @encode(short))) {
            result = [NSNumber numberWithShort:*((short *)returnValue)];
        }
        ...
        free(returnValue);
        
        return result;
    }
}

完整代碼如下:
HYInjectionCenter.h

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface HYInjectionCenter : NSObject

+ (id)invocationMethod:(NSString *)methodName
                object:(id)object
                params:(NSArray *)params;

+ (id)invocationMethod:(NSString *)methodName
             className:(NSString *)className
                params:(NSArray *)params;

@end

NS_ASSUME_NONNULL_END

HYInjectionCenter.m

#import "HYInjectionCenter.h"
#import <objc/runtime.h>

@implementation HYInjectionCenter

+ (id)invocationMethod:(NSString *)methodName object:(id)object params:(NSArray *)params {
    if (![methodName isKindOfClass:[NSString class]] || methodName.length == 0 || !object) {
        return nil;
    }
    
    SEL selector = NSSelectorFromString(methodName);
    //獲取對(duì)象方法簽名
    NSMethodSignature *signature = [object methodSignatureForSelector:selector];
    if (signature) {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
        invocation.target = object;
        invocation.selector = selector;
        
        //由于前兩個(gè)參數(shù)為隱式self和_cmd,所以需要從第三個(gè)參數(shù)開(kāi)始傳入顯式參數(shù)。
        NSUInteger paramCount = MIN(signature.numberOfArguments - 2, params.count);
        for (int i = 0; i < paramCount; i++) {
            id argument = params[i];
            const char *argumentType = [signature getArgumentTypeAtIndex:i + 2];
            if ([argument isKindOfClass:[NSNumber class]]) {
                if (!strcmp(argumentType, @encode(BOOL))) {
                    BOOL arg = [argument boolValue];
                    [invocation setArgument:&arg atIndex:i + 2];
                }
                else if (!strcmp(argumentType, @encode(int))) {
                    int arg = [argument intValue];
                    [invocation setArgument:&arg atIndex:i + 2];
                }
                else if (!strcmp(argumentType, @encode(short))) {
                    int arg = [argument intValue];
                    [invocation setArgument:&arg atIndex:i + 2];
                }
                else if (!strcmp(argumentType, @encode(long))) {
                    long arg = [argument longValue];
                    [invocation setArgument:&arg atIndex:i + 2];
                }
                else if (!strcmp(argumentType, @encode(long long))) {
                    long long arg = [argument longLongValue];
                    [invocation setArgument:&arg atIndex:i + 2];
                }
                else if (!strcmp(argumentType, @encode(float))) {
                    float arg = [argument floatValue];
                    [invocation setArgument:&arg atIndex:i + 2];
                }
                else if (!strcmp(argumentType, @encode(double))) {
                    double arg = [argument doubleValue];
                    [invocation setArgument:&arg atIndex:i + 2];
                }
                else if (!strcmp(argumentType, @encode(char))) {
                    char arg = [argument charValue];
                    [invocation setArgument:&arg atIndex:i + 2];
                }
                else if (!strcmp(argumentType, @encode(unsigned short))) {
                    unsigned short arg = [argument unsignedShortValue];
                    [invocation setArgument:&arg atIndex:i + 2];
                }
                else if (!strcmp(argumentType, @encode(unsigned int))) {
                    unsigned int arg = [argument unsignedIntValue];
                    [invocation setArgument:&arg atIndex:i + 2];
                }
                else if (!strcmp(argumentType, @encode(unsigned long))) {
                    unsigned long arg = [argument unsignedLongValue];
                    [invocation setArgument:&arg atIndex:i + 2];
                }
                else if (!strcmp(argumentType, @encode(unsigned long long))) {
                    unsigned long long arg = [argument unsignedLongLongValue];
                    [invocation setArgument:&arg atIndex:i + 2];
                }
                else if (!strcmp(argumentType, @encode(unsigned char))) {
                    unsigned char arg = [argument unsignedCharValue];
                    [invocation setArgument:&arg atIndex:i + 2];
                }
            }
            else {
                [invocation setArgument:&argument atIndex:i + 2];
            }
        }
        //引用參數(shù),防止在方法執(zhí)行完成之前參數(shù)釋放
        [invocation retainArguments];
        //執(zhí)行方法
        [invocation invoke];
        
        //判斷是否有返回值
        if (signature.methodReturnLength) {
            //獲取返回值類(lèi)型
            const char *returnType = signature.methodReturnType;
            //返回值類(lèi)型轉(zhuǎn)換
            if (!strcmp(returnType, @encode(void))) {//無(wú)返回值
                return nil;
            }
            else if (!strcmp(returnType, @encode(id))) {//對(duì)象類(lèi)型
                void *returnValue;
                [invocation getReturnValue:&returnValue];
                return (__bridge id)returnValue;
            }
            else {//基本數(shù)據(jù)類(lèi)型
                void *returnValue = (void *)malloc(signature.methodReturnLength);
                [invocation getReturnValue:returnValue];
                
                id result = nil;
                //根據(jù)類(lèi)型轉(zhuǎn)成NSNumber
                if (!strcmp(returnType, @encode(BOOL))) {
                    result = [NSNumber numberWithBool:*((BOOL *)returnValue)];
                }
                else if (!strcmp(returnType, @encode(int))) {
                    result = [NSNumber numberWithInt:*((int *)returnValue)];
                }
                else if (!strcmp(returnType, @encode(short))) {
                    result = [NSNumber numberWithShort:*((short *)returnValue)];
                }
                else if (!strcmp(returnType, @encode(long))) {
                    result = [NSNumber numberWithLong:*((long *)returnValue)];
                }
                else if (!strcmp(returnType, @encode(long long))) {
                    result = [NSNumber numberWithLongLong:*((long long *)returnValue)];
                }
                else if (!strcmp(returnType, @encode(float))) {
                    result = [NSNumber numberWithFloat:*((float *)returnValue)];
                }
                else if (!strcmp(returnType, @encode(double))) {
                    result = [NSNumber numberWithDouble:*((double *)returnValue)];
                }
                else if (!strcmp(returnType, @encode(char))) {
                    result = [NSNumber numberWithChar:*((char *)returnValue)];
                }
                else if (!strcmp(returnType, @encode(unsigned short))) {
                    result = [NSNumber numberWithUnsignedShort:*((unsigned short *)returnValue)];
                }
                else if (!strcmp(returnType, @encode(unsigned int))) {
                    result = [NSNumber numberWithUnsignedInt:*((unsigned int *)returnValue)];
                }
                else if (!strcmp(returnType, @encode(unsigned long))) {
                    result = [NSNumber numberWithUnsignedLong:*((unsigned long *)returnValue)];
                }
                else if (!strcmp(returnType, @encode(unsigned long long))) {
                    result = [NSNumber numberWithUnsignedLongLong:*((unsigned long long *)returnValue)];
                }
                else if (!strcmp(returnType, @encode(unsigned char))) {
                    result = [NSNumber numberWithUnsignedChar:*((unsigned char *)returnValue)];
                }
                free(returnValue);
                
                return result;
            }
        }
    }
    return nil;
}

+ (id)invocationMethod:(NSString *)methodName className:(NSString *)className params:(NSArray *)params {
    if (![className isKindOfClass:[NSString class]] || className.length == 0) {
        return nil;
    }
    return [self invocationMethod:methodName object:NSClassFromString(className) params:params];
}

@end
  1. 測(cè)試

創(chuàng)建一個(gè)HYManager類(lèi),類(lèi)中包含以下方法。

@interface HYManager : NSObject

- (void)param1:(NSString *)param1 param2:(BOOL)param2 param3:(NSInteger)param3;
- (NSString *)formatWithParam1:(NSString *)param1 param2:(NSInteger)param2;

- (BOOL)boolTest;
- (CGFloat)CGFloatTest;
- (NSInteger)integerTest;

+ (void)classFuncTest;

@end

@implementation HYManager

- (void)param1:(NSString *)param1 param2:(BOOL)param2 param3:(NSInteger)param3 {
    NSLog(@"%@ %d %zd", param1, param2, param3);
}

- (NSString *)formatWithParam1:(NSString *)param1 param2:(NSInteger)param2 {
    return [NSString stringWithFormat:@"%@-%zd", param1, param2];
}

- (BOOL)boolTest {
    return YES;
}

- (CGFloat)CGFloatTest {
    return 6.6f;
}

- (NSInteger)integerTest {
    return 7;
}

+ (void)classFuncTest {
    NSLog(@"class func test");
}

@end

用例1:有入?yún)⒌珶o(wú)返回值。

HYManager *manager = [[HYManager alloc] init];
[HYInjectionCenter invocationMethod:@"param1:param2:param3:"
                             object:manager
                             params:@[@"hy", @(YES), @(1)]];

輸出

hy YES 1

用例2:有入?yún)⒂蟹祷刂怠?/p>

id result = [HYInjectionCenter invocationMethod:@"formatWithParam1:param2:"
                                          object:manager
                                          params:@[@"hy", @(99)]];
NSLog(@"result: %@", result);

輸出

result: hy-99

用例3:返回值為基本數(shù)據(jù)類(lèi)型

id boolResult = [HYInjectionCenter invocationMethod:@"boolTest"
                                             object:manager
                                             params:nil];
id CGFloatResult = [HYInjectionCenter invocationMethod:@"CGFloatTest"
                                            object:manager
                                            params:nil];
id integerResult = [HYInjectionCenter invocationMethod:@"integerTest"
                                            object:manager
                                            params:nil];

NSLog(@"boolResult:%@", [boolResult boolValue] ? @"YES" : @"NO");
NSLog(@"CGFloatResult:%f ", [CGFloatResult floatValue]);
NSLog(@"integerResult:%zd", [integerResult integerValue]);

輸出

boolResult:YES
CGFloatResult:6.600000
integerResult:7

用例4:類(lèi)方法調(diào)用

[HYInjectionCenter invocationMethod:@"classFuncTest"
                          className:@"HYManager"
                             params:nil];

輸出

class func test

用例5:傳入?yún)?shù)小于方法參數(shù)個(gè)數(shù)

[HYInjectionCenter invocationMethod:@"param1:param2:param3"
                             object:manager
                             params:@[]];
(null) NO 0

補(bǔ)充

如果未按入?yún)㈩?lèi)型編碼判斷類(lèi)型并轉(zhuǎn)換之后賦值,則無(wú)法正確獲取參數(shù)。比如之前按以下的方式直接設(shè)置參數(shù)。

NSUInteger paramCount = MIN(signature.numberOfArguments - 2, params.count);
for (int i = 0; i < paramCount; i++) {
    id argument = params[i];
    [invocation setArgument:&argument atIndex:i + 2];
}

運(yùn)行用例1:

HYManager *manager = [[HYManager alloc] init];
[HYInjectionCenter invocationMethod:@"param1:param2:param3:"
                             object:manager
                             params:@[@"hy", @(YES), @(1)]];

最終輸出:

hy NO -2366665138710127672

可以看出,并非輸出預(yù)期的結(jié)果。所以即便繁瑣,也需要一一進(jìn)行參數(shù)類(lèi)型轉(zhuǎn)換。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,936評(píng)論 6 535
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,744評(píng)論 3 421
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,879評(píng)論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,181評(píng)論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,935評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,325評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,384評(píng)論 3 443
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,534評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,084評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,892評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,067評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,623評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,322評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,735評(píng)論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,990評(píng)論 1 289
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,800評(píng)論 3 395
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,084評(píng)論 2 375

推薦閱讀更多精彩內(nèi)容