1. 什么是方法签名?
Java中的方法签名是由方法名称
和一个参数列表
(方法的参数的顺序和类型)组成, 不包含方法的返回值.
iOS中的方法签名是通过NSMethodSignature
实现的, 下面我们看看NSMethodSignature
的常用方法和属性
1.1 NSMethodSignature自己的方法
//手动初始化方法
+ (nullable NSMethodSignature *)signatureWithObjCTypes:(const char *)types;
使用:
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:"];
signatureWithObjCTypes
的参数可查看官方文档的相关介绍
问题----------------
我们上面说方法的签名是由
方法名称
和一个参数列表
(方法的参数的顺序和类型)组成, 不包含方法的返回值.
但iOS中的方法签名似乎不一样, 不涉及方法名
, 但是包含方法的返回值类型(void)
,参数类型(obj, SEL)
, 真的是这样么??
我们需要保留这个问题, 在实际使用过程中或许能够明白这个问题.
1.2 NSObject中的方法
除了NSMethodSignature
类中包含创建NSMethodSignature
对象的方法, NSObject
中也包含获取NSMethodSignature
对象的方法
//1. 获取类方法或者实例方法签名, 无论是对象还是类对象都能调用该方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
//2. 只能获取实例方法签名
+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector
值得注意的是, NSObject
中的方法, NSObject
的子类都可以调用, 所以一般有三种情况
- 从类中获取类方法签名
- 从类中获取实例方法签名
- 从实例中获取实例方法签名
但是不能从实例中获取类方法签名, 因为类方法是存放在元类中的, 实例只能获取实例对应的类对象中的方法
1.2.1 从类中获取类方法签名
和实例方法签名
NSMethodSignature *s1 = [NSString methodSignatureForSelector:@selector(alloc)];
NSMethodSignature *s2 = [NSString methodSignatureForSelector:@selector(init)];
NSLog(@"--s1:%@",s1);
NSLog(@"--s2:%@",s2);
结果:
我们可以发现, 从不同的方法获取到的方法签名, 为什么是一样的?
因为方法虽然不同, 但是方法的签名是一样的(同一个
NSMethodSignature
), 即方法的
返回值类型
和
参数类型
是一样的
1.2.2 从实例对象中可获取实例方法签名
, 不能获取类方法签名
NSString *str = @"string...";
NSMethodSignature *s3 = [str methodSignatureForSelector:@selector(alloc)];
NSMethodSignature *s4 = [str methodSignatureForSelector:@selector(init)];
NSLog(@"--s3:%@",s3);
NSLog(@"--s4:%@",s4);
结果:
1.2.3 instanceMethodSignatureForSelector
只能获取实例方法的签名
NSMethodSignature *s5 = [NSString instanceMethodSignatureForSelector:@selector(alloc)];
NSMethodSignature *s6 = [NSString instanceMethodSignatureForSelector:@selector(init)];
NSLog(@"--s5:%@",s5);
NSLog(@"--s6:%@",s6);
结果:
2. 如何使用方法签名
- (void)signatureTest {
//1. 创建方法签名
NSMethodSignature *s9 = [NSMethodSignature signatureWithObjCTypes:"v@:ii"];
//2. 根据方法签名创建一个NSInvocation对象
NSInvocation *invo = [NSInvocation invocationWithMethodSignature:s9];
//3. 为NSInvocation对象设置接收消息的对象
[invo setTarget:self];
//4. 为NSInvocation对象设置发送的消息
[invo setSelector:@selector(test11:)];
//5. 为NSInvocation对象设置一个新的参数
NSInteger a = 1;
[invo setArgument:&a atIndex:2];
//6. 调用NSInvocation
[invo invoke];
}
- (void)test11:(int )a{
NSLog(@"-----:%d",a);
}
结果:
2018-08-16 11:19:39.493722+0800 RuntimeNew[4527:236471] -----:1
解释:
- 我们一开始创建的方法签名是有4个参数 @ : i i, 除了前两个是每个方法都有的, 我们又添加了两个int类型的参数, 但是我们的方法
- (void)test11:(int )a
中并只有一个参数, 依然可以执行, 说明签名函数的参数数量
大于被调用的函数参数数量
时, 也是可以正常调用的.
3. iOS中的方法签名是否包含方法名?
我们可以从两方面去看待这个问题
3.1 我们可以从上面signatureTest
方法的第四步: 为NSInvocation对象设置发送的消息
[invo setSelector:@selector(test11:)];
我们在已经拥有方法签名的情况下, 还是需要添加Selector, 说明我们还是需要方法名称
的, 而不仅仅是返回值类型
和参数类型
3.2 这一点, 我们在Runtime
的方法转发机制中也有体现
- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(testMethod:)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return YES;//返回YES,进入下一步转发
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
return nil;//返回nil,进入下一步转发
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if ([NSStringFromSelector(aSelector) isEqualToString:@"testMethod:"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = anInvocation.selector;
Person *p = [Person new];
if([p respondsToSelector:sel]) {
NSString *str = @"Lucy";
[anInvocation setArgument:&str atIndex:2];
[anInvocation invokeWithTarget:p];
}
else {
[self doesNotRecognizeSelector:sel];
}
}
- 我们希望
self
执行testMethod
, 但是self
并没有实现该方法, 所以我们要一步一步去找 -
resolveInstanceMethod:
返回YES, 意味着我们没有为testMethod
添加函数实现, 执行下一步forwardingTargetForSelector
-
forwardingTargetForSelector
返回nil, 意味着我们没有为该方法提供备用接收者
, 所以只能继续执行下一步, 通过方法签名和NSInvocation来实现完整的消息转发 -
methodSignatureForSelector
拿到方法签名---在forwardInvocation
指定一个对象, 判断该对象是否实现了此方法. 实现了, 则执行; 未实现, 则调用doesNotRecognizeSelector
方法
问题就出在这一步, 如果我们iOS中的方法签名只看方法的返回值类型
和参数类型
, 我们知道, 一个类中, 有很多方法的返回值类型
和参数类型
是相同的, 这样会出现什么情况? 调用多个方法?
所以我们必定是要把方法名
作为方法签名的重要一部分的.实际上, 我们在forwardInvocation
方法中是拿到了方法名的, 我们可以输出一下看看
SEL sel = anInvocation.selector;
NSLog(@"%s",anInvocation.selector);
综上, iOS中的方法签名是包含方法名的, 只是有时候不是那么明显