zoukankan      html  css  js  c++  java
  • 利用objc的runtime来定位次线程中unrecognized selector sent to instance的问题

    昨天遇到一个仅仅有一行错误信息的问题:

    -[NSNull objectForKey:]: unrecognized selector sent to instance 0x537e068

    因为这个问题发生在次线程。所以没有太实用的堆栈信息。而是仅仅有简单的SIGABRT信息:



    考虑到unrecognized selector sent to instance这类问题是因为向某个对象发送了未实现的消息,这个过程大致例如以下(图片摘自这里):


    參考Objective-C的对象模型:

    struct objc_class {
        Class isa  OBJC_ISA_AVAILABILITY;
    
    #if !__OBJC2__
        Class super_class                                        OBJC2_UNAVAILABLE;
        const char *name                                         OBJC2_UNAVAILABLE;
        long version                                             OBJC2_UNAVAILABLE;
        long info                                                OBJC2_UNAVAILABLE;
        long instance_size                                       OBJC2_UNAVAILABLE;
        struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
        struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
        struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
        struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
    #endif
    
    } OBJC2_UNAVAILABLE;
    /* Use `Class` instead of `struct objc_class *` */

    消息发送的流程大致例如以下:

    • 推断发送的消息是否为retain等内存管理方法。
    • 推断receiver是否为nil;
    • 推断是否在方法缓存中,即struct objc_cache *cache;
    • 推断是否在方法列表中,即struct objc_method_list **methodLists,因为对象的方法能够动态加入,所以这里的类型是struct objc_method_list **,能够參考objc-class.m源文件。
    • 推断是否在继承体系中——到这里,称之为Messaging过程。
    • 假设实在找不到。就运行Dynamic Method Resolution过程,即尝试调用resolveInstanceMethod:或resolveClassMethod:方法,我们能够通过实现这两个方法来动态加入方法;
    • 动态方法解析过程假设返回NO,那么还有最后的解救机会,就是Message Forwarding消息转发过程(參考NSObject.h):

    - (id)forwardingTargetForSelector:(SEL)aSelector;
    - (void)forwardInvocation:(NSInvocation *)anInvocation;
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;

    我第一反应是加入resolveInstanceMethod:来观察,这是一个类方法。所以得加入到metaClass上:

    Class metaClass = objc_getMetaClass("NSNull");
    SEL sel = @selector(resolveInstanceMethod:);
    const char *type = "c@::";
    class_addMethod(metaClass, sel, (IMP)resolveInstanceMethod, type);

    但遗憾的是,即便此时方法寻找不到时会调用到resolveInstanceMethod:方法。只是在设置的断点位置看也已经没有明白的堆栈信息了。所以我就直接加入找不到的方法objectForKey:来定位:

    Class metaClass = objc_getMetaClass("NSNull");
    SEL sel = @selector(objectForKey:);
    const char *type = "@@:@";
    class_addMethod(metaClass, sel, (IMP)objectForKey, type);

    样一来。通过在我们加入的objectForKey方法中设置断点就能够获取到具体堆栈信息,从而进一步定位到问题所在:
    {
      fromId = "U6d4bU8bd520#U65faU4f01U65e0U7ebfU6d4bU8bd5";
      msgContent = "<null>";
      msgSendTime = 1402909302;
      msgType = 12;
      uuid = 0;
    }

    原因是因为服务端推送的消息中一个必填字段为空。而client也刚好在此处没有使用项目代码中约定的类型检查宏(此处应为VFDict),而是直接当做NSDictionary来操作。

  • 相关阅读:
    get 传 json 数据
    go text/template html/template invalid memory address or nil pointer dereference
    (转)go语言变参,匿名函数的多种用法
    shell 定义变量 坑
    python3 使用aria2下载的一个脚本
    python3 selenium 超时停止加载,并且捕捉异常, 进行下一步【亲测有效】
    selenium学习笔记——driver.get(url) 页面加载时间太长
    python Selenium chromedriver 自动化超时报错:你需要使用多标签保护罩护体
    selenium等待
    小数据池与代码块
  • 原文地址:https://www.cnblogs.com/mfrbuaa/p/5084719.html
Copyright © 2011-2022 走看看