注:如果有描述或理解错误的情况,望不吝指正!
当调用一个未实现的方法时,通常会得到以下错误:
unrecognized selector sent to instance
本文主要记录这种情况下,如何借助 runtime 实现方法动态解析。动态解析过程如下图所示:
方法解析的调用顺序如上图所示,如果消息处理在调用链的后面环节,则前面环节的步骤不可缺少,否则同样产生崩溃异常。
初始说明
为了便于说明问题,简单定义了个person类,并且未做任何实现。
@interface Person : NSObject
@property(assign,nonatomic) NSInteger age;
-(void)modifyAge:(NSInteger)age;
@end
@implementation Person
@end
如果直接调用 Person
的 modifyAge:
方法会产生崩溃异常!
动态方法解析
resolveInstanceMethod
@implementation Person
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(modifyAge:)) {
class_addMethod([Person class], sel, (IMP)modify, "v@:@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void modify(id obj,SEL _cmd,NSInteger age){
((Person *)obj).age = age;
printf("方法解析成功\n");
}
@end
动态方法解析的核心是 class_addMethod
,它把函数和它对应的实现绑定在一起。
PS:
1.如果 modifyAge:name 是多个参数的情况,modify(id obj,SEL _cmd,NSInteger age,NSString *name)。
2."v@:@"参数即使为"",依然可以成功解析参数,原因未知?
Xcode版本:12.5
消息转发1
forwardingTargetForSelector:
@implementation Person
+ (BOOL)resolveInstanceMethod:(SEL)sel{
return [super resolveInstanceMethod:sel];
}
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(modifyAge:)) {
return [[Xiaoming alloc]init];
}
return [super forwardingTargetForSelector:aSelector];
}
@end
@interface Xiaoming : Person
@end
@implementation Xiaoming
- (void)modifyAge:(NSInteger)age{
NSLog(@"消息转发成功 , 参数:%ld",age);
}
@end
消息转发1的过程,可以看成是把消息的处理指定到特定对象中。
消息转发2
methodSignatureForSelector:
forwardInvocation:
@implementation Person
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"resolveInstanceMethod : %@",NSStringFromSelector(sel));
return [super resolveInstanceMethod:sel];
}
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"forwardingTargetForSelector : %@",NSStringFromSelector(aSelector));
return [super forwardingTargetForSelector:aSelector];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"methodSignatureForSelector : %@",NSStringFromSelector(aSelector));
if (aSelector == @selector(modifyAge:)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"forwardInvocation : %@",NSStringFromSelector(anInvocation.selector));
Xiaoming *ming = [[Xiaoming alloc]init];
if ([ming respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:ming];
}else{
[self doesNotRecognizeSelector:anInvocation.selector];
}
}
@end
消息转发2的过程是先做了函数签名,然后再把消息指定到特定对象中进行处理。
代码执行后,打印信息如下:
2021-05-31 12:16:26.101895+0800 Test[1994:138331] resolveInstanceMethod : modifyAge:
2021-05-31 12:16:26.102164+0800 Test[1994:138331] forwardingTargetForSelector : modifyAge:
2021-05-31 12:16:26.102385+0800 Test[1994:138331] methodSignatureForSelector : modifyAge:
2021-05-31 12:16:26.102610+0800 Test[1994:138331] resolveInstanceMethod : _forwardStackInvocation:
2021-05-31 12:16:26.102906+0800 Test[1994:138331] forwardInvocation : modifyAge:
不难发现 forwardInvocation
方法的调用是通过方法动态解析的。
参考
iOS 中的runtime与消息转发