美文网首页
iOS Runtime 消息转发

iOS Runtime 消息转发

作者: 再好一点点 | 来源:发表于2019-02-12 20:40 被阅读0次

    一:  iOS开发常见的一个崩溃信息就是unrecognized selector sent to instance,这是因为调用了不存在的方法导致的(比如字典当做数组来用,使用了下标.  数组当做字典来用,调用了键值对取值),在所有的崩溃中占有相当大的比例

    二: 还有一种占比比较高的闪退就是NULL,本来和后台说好的数据类型不会错,但是呢,总是不如意啊.尤其是php当没有数据的时候字典就变成数组了,这样的话客户端解析一不小心就JJ了

    不过不怕,咱有黑科技,接下来请往下看

    消息转发流程:

    下面先来解决第一种情况

    1,首先在ViewController类中创建对象以及调用person的sendMessage方法

    @implementation ViewController

    - (void)viewDidLoad {

        [super viewDidLoad];

        Person*p = [[Person alloc] init];

        [p1 performSelector:NSSelectorFromString(@"sendMessage") withObject:nil];   

    }

    @interface Person : NSObject

    @end

    #import "Person.h"

    @implementation Person

    @end

    Student类:

    @interface Student :NSObject

    @end

    #import "Student.h"

    @implementation Student

    @end

    当 Runtime 系统在 Cache 和类的方法列表(包括父类)中找不到要执行的方法时,Runtime 会调用 resolveInstanceMethod: 或 resolveClassMethod: 来给我们一次动态添加方法实现的机会。我们需要用 class_addMethod 函数完成向特定类添加特定方法实现的操作:

    动态方法解析

    在Person.m中实现一下方法就可以做到不闪退了.

    但是这样做不通用,我们可以搞一个继承自NSObject的类,其他的类都继承与这个类,这样只需要在父类中实现以下方法就可以做到不闪退了.不可在分类中实现此方法,会报错的

    v@:中, v表示返回值void, @表示对象self, :表示SEL

    //在.m中实现这两个方法:

    -(void)noObjMethod{   

        NSLog(@"未实现这个实例方法");

    }

    +(void)noClassMethod{   

        NSLog(@"未实现这个类方法");

    }

    //并且重写消息转发的方法:

    // 当一个对象调用未实现的方法,会调用这个方法处理,并且会把对应的方法列表传过来.

    //注意:实例方法是存在于当前对象对应的类的方法列表中

    +(BOOL)resolveInstanceMethod:(SEL)sel{   

        SEL aSel = NSSelectorFromString(@"noObjMethod");   

        Method aMethod = class_getInstanceMethod(self, aSel);   

        class_addMethod([self class], sel, method_getImplementation(aMethod), method_getTypeEncoding(method));   

        return YES;

    }

    重定向

    2,如果resolveInstanceMethod 返回值为NO,会执行- (id)forwardingTargetForSelector:(SEL)aSelector,此方法会将消息转发给Student类实现

    - (id)forwardingTargetForSelector:(SEL)aSelector {

        if(aSelector ==@selector(sendMessage:)) {

            return [Student new];

        }else{

            return  [super forwardingTargetForSelector:aSelector];

        }

    }

    转发

    3,如果- (id)forwardingTargetForSelector:(SEL)aSelector返回值为nil,则会调用以下方法

    - (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector {

        if(aSelector ==@selector(sendMessage:)) {

            return [NSMethodSignature signatureWithObjCTypes:"V@:@"];

        }

        return nil;

    }

    - (void)forwardInvocation:(NSInvocation*)anInvocation {

        SEL sel = [anInvocation selector];

        Student *stu = [Student new];

        if([stu respondsToSelector:sel]) {

            [anInvocation invokeWithTarget:stu];

        }else{

            [super forwardInvocation:anInvocation];

        }

    }

    注:- (void)forwardInvocation:(NSInvocation*)anInvocation 方法实现可以将消息转发给多个对象实现

    如果以上三种重载都没执行消息,此时会调用- (void)doesNotRecognizeSelector:(SEL)aSelector方法,此时程序会崩溃

    - (void)doesNotRecognizeSelector:(SEL)aSelector {

        NSLog(@"doesNotRecognizeSelector");

    }

    从流程图可以看出,越是往后开销越大,所以在早期做出预防处理是最好的选择

    所以直接在父类中重写resolveInstanceMethod方法,就可以做到程序不会崩溃了

    接下来解决第二种情况,就是NULL的情况

    这时候可以搞一个分类,如下:

    #import <Foundation/Foundation.h>

    @interface NSNull (Exception)

    @end

    #import "NSNull+Exception.h"

    #import <objc/runtime.h>

    @implementation NSNull (Exception)

    #define pLog

    #define JsonObjects @[@"",@0,@{},@[]]

    //在.m中实现这两个方法:

    -(void)noObjMethod{

        NSLog(@"未实现这个实例方法");

    }

    +(void)noClassMethod{

        NSLog(@"未实现这个类方法");

    }

    +(BOOL)resolveInstanceMethod:(SEL)sel{

        for(id jsonObj in JsonObjects) {

            if([jsonObj respondsToSelector:sel]) {

    #ifdef pLog

                NSLog(@"NULL出现啦!这个对象应该是是_%@",[jsonObj class]);

    #endif

            }

        }

        SEL aSel = NSSelectorFromString(@"noObjMethod");

        Method aMethod = class_getInstanceMethod(self, aSel);

        class_addMethod([self class], sel, method_getImplementation(aMethod), "v@:");

        return YES;

    }

    如果此时在 ViewController.m中调用一下错误的字典 则不会引起崩溃

    NSDictionary* dict = [[NSNull alloc] init];

    [dict objectForKey:@"123"];

    控制台会打印出以下信息:

    2019-03-02 09:44:30.905674+0800 Test[28763:7642018] NULL出现啦!这个对象应该是是___NSDictionary0

    2019-03-02 09:44:30.905790+0800 Test[28763:7642018] 未实现这个实例方法

    现在就可以随意折腾了

    好了,至此咱们的APP就可以减少大部分闪退问题了

    Demo地址:GitHub - yeshibuzhong/iOS 中的Runtime_01

    相关文章

      网友评论

          本文标题:iOS Runtime 消息转发

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