美文网首页ios developers
iOS 消息转发防崩溃解析

iOS 消息转发防崩溃解析

作者: Balopy | 来源:发表于2019-11-01 16:02 被阅读0次

    Objective-C实例(类)对象调用一个方法,会首先在本类方法列表查找,如果没有,会在父类查找,直到根类NSObject,在任何一层找到方法就会立即执行,如果到了最后根类NSObject还没有找到,才会触发Objective-C Runtime的消息转发机制,最后触发 doesNotRecognizeSelector:.

    拦截异常有三次机会:

      1. +(BOOL)resolveInstanceMethod:(SEL)sel { }

    如果当前对象调用了一个不存在的方法,Runtime会调用resolveInstanceMethod:来进行动态方法解析, 我们需要用class_addMethod函数完成向特定类添加特定方法实现的操作,返回false,完成方法替换,结束消息转发,返回true,则进入下一步forwardingTargetForSelector:重定向.

    + (BOOL)resolveInstanceMethod:(SEL)sel {
    
       NSLog(@"1: 方法不存在,进入2");
       
       NSString *selector = NSStringFromSelector(sel);
       
       Class class = [self class];
       if ([selector isEqualToString:@"foo"]) {
           class_addMethod(class, sel, (IMP)fooMethod, "v@:@");
           return false;
       }
       return true;
    }
    
      1. -(id)forwardingTargetForSelector:(SEL)aSelector { }

    如果步骤1没有实现拦截,返回true表示继续消息转发,在消息转发机制执行前,Runtime 系统会再给我们一次重定向的机会,通过重载forwardingTargetForSelector:方法来替换消息的接受者为其他对象,返回有值,结束转发。返回nil,则进步下一步forwardInvocation:.

    - (id)forwardingTargetForSelector:(SEL)aSelector {
        
        NSLog(@"2: 方法不存在,进入3:");
        
        NSString *selector = NSStringFromSelector(aSelector);
        
        Class class = [self class];
        if ([selector isEqualToString:@"other"]) {
            class_addMethod(class, aSelector, (IMP)otherMethod, "v@:@");
            return [BLSon new];
        }
        
        return [super forwardingTargetForSelector:aSelector];
    }
    
      1. 如果步骤1步骤2都没有完成消息转发,则还有最后一条保命大法。
    //获取方法签名进入下一步,进行消息转发
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { }
    //进行消息转发
    - (void)forwardInvocation:(NSInvocation *)anInvocation { }
    

    通常forwardInvocation:,会用一个已知类,并且能响应异常方法的对象进行拦截。

    - (void)forwardInvocation:(NSInvocation *)anInvocation {
       
        SEL sel = anInvocation.selector;
      
        BLFather *father = [BLFather new];
        
        NSLog(@"4: 方法不存在,进入5:");
        
        if ([father respondsToSelector:sel])    {
            [anInvocation invokeWithTarget:father];
        }
    }
    

    完整代码:

    
    #import "BLSon.h"
    #import <objc/runtime.h>
    
    @implementation BLSon
    
    /*
     如果当前对象调用了一个不存在的方法
     Runtime会调用resolveInstanceMethod:来进行动态方法解析
     我们需要用class_addMethod函数完成向特定类添加特定方法实现的操作
     返回NO,则进入下一步forwardingTargetForSelector:
     */
    + (BOOL)resolveInstanceMethod:(SEL)sel {
    
        NSLog(@"1: 方法不存在,进入2");
        
        NSString *selector = NSStringFromSelector(sel);
        Class class = [self class];
    
        if ([selector isEqualToString:@"foo"]) {
            class_addMethod(class, sel, (IMP)fooMethod, "v@:@");
            return false;
        }
        return true;
    }
    
    void fooMethod () {
        
        NSLog(@"这是个新方法");
    }
    
    void otherMethod () {
        
        NSLog(@"这是个other方法");
    }
    
    /*
     在消息转发机制执行前,Runtime 系统会再给我们一次重定向的机会
     通过重载forwardingTargetForSelector:方法来替换消息的接受者为其他对象
     返回nil则进步下一步forwardInvocation:
     */
    - (id)forwardingTargetForSelector:(SEL)aSelector {
        
        NSLog(@"2: 方法不存在,进入3:");
        
        NSString *selector = NSStringFromSelector(aSelector);
        
        Class class = [self class];
        if ([selector isEqualToString:@"other"]) {
            class_addMethod(class, aSelector, (IMP)otherMethod, "v@:@");
            return [BLSon new];
        }
        
        return [super forwardingTargetForSelector:aSelector];
    }
    
    /*
     获取方法签名进入下一步,进行消息转发
     */
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
      
        NSLog(@"3: 方法不存在,进入4:");
    
        NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];
       
        if (!methodSignature) {
       
            methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:*"];
        }
        return methodSignature;
    }
    
    
    /*
     消息转发
     */
    - (void)forwardInvocation:(NSInvocation *)anInvocation {
       
        SEL sel = anInvocation.selector;
      
        BLFather *father = [BLFather new];
        
        NSLog(@"4: 方法不存在,进入5:");
        
        if ([father respondsToSelector:sel])
        {
            [anInvocation invokeWithTarget:father];
        }
        //    这里可以添加一些操作,用于定位异常,或自定义替换方法
        //    else {
        //        [anInvocation doesNotRecognizeSelector:sel];
        //    }
    }
    
    @end
    
    

    相关文章

      网友评论

        本文标题:iOS 消息转发防崩溃解析

        本文链接:https://www.haomeiwen.com/subject/btzfbctx.html