iOS runtime 消息转发学习记录

注:如果有描述或理解错误的情况,望不吝指正!

当调用一个未实现的方法时,通常会得到以下错误:
unrecognized selector sent to instance
本文主要记录这种情况下,如何借助 runtime 实现方法动态解析。动态解析过程如下图所示:


方法解析的调用顺序如上图所示,如果消息处理在调用链的后面环节,则前面环节的步骤不可缺少,否则同样产生崩溃异常。

初始说明

为了便于说明问题,简单定义了个person类,并且未做任何实现。

@interface Person : NSObject
@property(assign,nonatomic) NSInteger age;
-(void)modifyAge:(NSInteger)age;
@end

@implementation Person
@end

如果直接调用 PersonmodifyAge: 方法会产生崩溃异常!

动态方法解析

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与消息转发

你可能感兴趣的:(iOS runtime 消息转发学习记录)