NSMethodSignature
NSMethodSignature主要是方法的返回值和參數(shù)的類(lèi)型信息的記錄。
方法簽名由方法返回類(lèi)型的一個(gè)或多個(gè)字符組成,后跟隱式參數(shù)self
和_cmd
的字符串編碼,還有零個(gè)或多個(gè)顯式參數(shù)。可以使用methodReturnType
和methodReturnLength
屬性確定字符串編碼和返回類(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)該使用alloc
和init
創(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不支持歸檔。
使用
- 獲取對(duì)象方法簽名。
NSMethodSignature *signature = [object methodSignatureForSelector:selector];
- 根據(jù)方法簽名創(chuàng)建NSInvocation對(duì)象。
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
- 設(shè)置執(zhí)行方法的對(duì)象和方法選擇器。
invocation.target = object;
invocation.selector = selector;
- 設(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等等。
- 引用參數(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];
- 獲取返回值并根據(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
- 測(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)換。