OC中方法调用有三种:
第一种:直接调用
- (void)viewDidLoad {
[super viewDidLoad];
[self printStr1:@"hello"];
}
- (void)printStr1:(NSString*)str{
NSLog(@"printStr1 %@",str);
}
第二种:通过performSelector
- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(printStr1:) withObject:@"hello world"];
}
- (void)printStr1:(NSString*)str{
NSLog(@"printStr1 %@",str);
}
第三种:NSMethodSignature 和NSInvocation
//1.获取方法签名
NSMethodSignature *sigOfPrintStr = [self methodSignatureForSelector:@selector(printStr1:)];
//2.获取方法签名对应的invocation
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sigOfPrintStr];
/**
3.设置消息接受者,与[invocationOfPrintStr setArgument:(__bridge void * _Nonnull)(self) atIndex:0]等价
*/
[invocation setTarget:self];
/**4.设置要执行的selector。与[invocationOfPrintStr setArgument:@selector(printStr1:) atIndex:1] 等价*/
[invocation setSelector:@selector(printStr1:)];
//5.设置参数
NSString *str = @"hello world";
[invocation setArgument:&str atIndex:2];
//开始执行
[invocation invoke];
NSMethodSignature概述
NSMethodSignature和NSInvocation是Foundation框架为咱们提供的一种调用方法的方式,常常用于消息转发。
NSMethodSignature用于描述method的类型信息:返回值类型,及每一个参数的类型。 能够经过下面的方式进行建立:
创建方法有三种:
@interface NSObject
//获取实例方法的签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
//获取类方法的签名
+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector;
@end
//使用ObjCTypes建立方法签名
@interface NSMethodSignature
+ (nullable NSMethodSignature *)signatureWithObjCTypes:(const char *)types;
@end
其实ObjcTypes就是 “v@😡”。它是一个是字符串数组,该数组包含了方法的类型编码。
比如有下面这样一个方法:
- (void)goToSchoolWithPerson:(Person *)person;
[zhangsan goToSchoolWithPerson:lisi];
我们都知道消息发送会被转换成objc _ msgSend(id reciever,SEL sel,prarams1,params2,…)。所以上面的方法会被转换成:
void objc_msgSend(zhangsan,@selector(goToSchoolWithPerson:),lisi); //包含两个隐藏参数
这里的 “v@😡”就代表:
- “v”:代表返回值void
- “@”:代表一个对象,这里指代的id类型zhangsan,也就是消息的receiver
- “:”:代表SEL
- “@”:代表参数lisi
NSMethodSignature的一种应用
数据模型解析中对null数据的处理
#import "NSNull+BCMNull.h"
@implementation NSNull (BCMNull)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];
#ifdef DEBUG
//抛出错误调用栈信息
NSLog(@"=========NSNull Message=================\n%@", [NSThread callStackSymbols]);
#else
#endif
NSArray *someClass = @[[NSMutableArray class],
[NSMutableString class],
[NSMutableDictionary class],
[NSDate class],
[NSNumber class],
[NSData class]];
///> 如果签名方法不存在,则生成一个签名方法
if (!methodSignature) {
for (Class class in someClass) {
if ([class instancesRespondToSelector:aSelector]) {
methodSignature = [class instanceMethodSignatureForSelector:aSelector];
}
}
}
return methodSignature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
anInvocation.target = nil;
[anInvocation invoke];
}
@end
举例:
建立一个TestModel类,只声明testMethod,而不去实现,如果TestModel对象调用testMethod时会crash。
如果不想crash,就必须实现methodSignatureForSelector和forwardInvocation,在methodSignatureForSelector方法里面返回一个对应的方法签名。
#import "TestModel.h"
@implementation TestModel
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if(aSelector == @selector(testMethod))
{
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return nil;
}
-(void)forwardInvocation:(NSInvocation *)anInvocation
{
}
-(void)testMethod2 {
NSLog(@"调用了方法2");
}
@end
当然你也可以在forwardInvocation方法里面写一些其他内容,
新建一个TestModelHelper1类,声明且实现testMethod方法:
#import "TestModelHelper1.h"
@implementation TestModelHelper1
-(void)testMethod
{
NSLog(@"i am TestModelHelper1");
}
@end
在forwardInvocation里面增加如下代码:
#import "TestModel.h"
#import "TestModelHelper1.h"
@implementation TestModel
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if(aSelector == @selector(testMethod))
{
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return nil;
}
-(void)forwardInvocation:(NSInvocation *)anInvocation
{
if (anInvocation.selector == @selector(testMethod))
{
TestModelHelper1 *h1 = [[TestModelHelper1 alloc] init];
[anInvocation invokeWithTarget:h1];
}
}
-(void)testMethod2 {
NSLog(@"调用了方法2");
}
@end
控制台输出:
2021-05-21 13:46:25.716145+0800 TestMethodSignature[12594:313045] i am TestModelHelper1
由此,我们得出方法调用顺序:
向一个实例发送一个消息后,系统是处理的流程:
1.系统会check是否能response这个消息,如果没有,系统就会发出methodSignatureForSelector消息,寻问它这个消息是否有效?有效就返回对应的方法地址之类的,无效则返回nil。如果是nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了. 如果不是nil接着发送forwardInvocation消息。
2.如果能response则调用相应方法。