iOS 消息转发防崩溃解析

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 

@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 消息转发防崩溃解析)