面试精品iOS开发

iOS - 动态添加方法和消息转发

2017-12-15  本文已影响12人  壮骨
假设

假设,声明一个 TestObect 的类,.h中声明了 test方法,但是.m中未实现,但是调用了这个方法会报一个经典的错误

没有找到方法实现

ok,前提已经说完了,我们就从找这个错误原因讲起。

解决

首先,该方法在调用时,系统会查看这个对象能否接收这个消息(查看这个类有没有这个方法,或者有没有实现这个方法。),如果不能并且只在不能的情况下,就会调用下面这几个方法,给你“补救”的机会,你可以先理解为几套防止程序crash的备选方案,我们就是利用这几个方案进行消息转发,注意一点,前一套方案实现后一套方法就不会执行。如果这几套方案你都没有做处理,那么程序就会报错crash。

方案一:
+ (BOOL)resolveInstanceMethod:(SEL)sel;
方案二:
- (id)forwardingTargetForSelector:(SEL)aSelector;
方案三:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;

抽象解释一下消息转发: 比赛足球时,脚下有球的那名球员,如果他的位置不利于射门或者他的球即将被对方球员抢断,这时最好是把球传出去,这里的球就相当于消息。

1.resolveInstanceMethod

当你调用方法时,系统会调用 resolveInstanceMethod 这个方法,参数SEL就是你调用的方法,那么我们可以重写这个方法,从而实现动态添加一个方法,来使程序不会崩溃.

// 需要导入 #import <objc/runtime.h>
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(test)) {        
        IMP imp = class_getMethodImplementation(self, @selector(run));
        class_addMethod([self class], sel, imp, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
动态添加方法
科普一下
  1. IMP获取
    如果添加方法实现是用OC风格写的 那么用 class_getMethodImplementation 来获取IMP
    如果是用C风格写的函数
void run(id self, SEL _cmd) {
    NSLog(@" %@ %s", sel_getName(_cmd));
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(test)) {
        class_addMethod([self class], sel, (IMP)run, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
  1. class_addMethod 参数
2.forwardingTargetForSelector

forwardingTargetForSelector 是NSObject的函数,来决定谁执行方法

TestObect.m
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    return [super resolveInstanceMethod:sel];
}

- (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(test)) {
        return [ForwardTestObect new];
    }
    return [super forwardingTargetForSelector:aSelector];
}

ForwardTestObect.m
- (void)test{
    NSLog(@"%@  --- test ---",self);
}
打印截图
3.methodSignatureForSelector和forwardInvocation

methodSignatureForSelector用来生成方法签名,这个签名就是给forwardInvocation中的参数NSInvocation调用的。
开头我们要找的错误unrecognized selector sent to instance原因,原来就是因为methodSignatureForSelector这个方法中,由于没有找到run对应的实现方法,所以返回了一个空的方法签名,最终导致程序报错崩溃。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (aSelector == @selector(test)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    SEL sel = [anInvocation selector];
    //创建接受对象
    ForwardTestObect *forward = [ForwardTestObect new];
    //判断是否实现了这个方法
    if ([forward respondsToSelector:sel]) {
        // 唤醒这个方法
        [anInvocation invokeWithTarget:forward];
    }else{
        [super forwardInvocation:anInvocation];
    }
}
打印截图

其中: [NSMethodSignature signatureWithObjCTypes:"v@:"]; 方法的参数参考上述 class_addMethod 方法的参数四
forwardInvocation: 方法就是一个不能识别消息的分发中心,将这些不能识别的消息转发给不同的接收对象,或者转发给同一个对象,再或者将消息翻译成另外的消息,亦或者简单的“吃掉”某些消息,因此没有响应也不会报错。这一切都取决于方法的具体实现。
注意:forwardInvocation:方法只有在消息接收对象中无法正常响应消息时才会被调用。所以,如果我们向往一个对象将一个消息转发给其他对象时,要确保这个对象不能有该消息的所对应的方法。否则,forwardInvocation:将不可能被调用。

最后

1、调用resolveInstanceMethod给个机会让类添加这个实现这个函数
2、调用forwardingTargetForSelector让别的对象去执行这个函数
3、调用methodSignatureForSelector(函数符号制造器)和forwardInvocation(函数执行器)灵活的将目标函数以其他形式执行。
如果都不中,调用doesNotRecognizeSelector抛出异常。

方案1是动态添加方法,方案2和方案3 就是OC的消息转发机制

上一篇 下一篇

猜你喜欢

热点阅读