利用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 = "[email protected]::";
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 = "\U6d4b\U8bd520#\U65fa\U4f01\U65e0\U7ebf\U6d4b\U8bd5";
  msgContent = "<null>";
  msgSendTime = 1402909302;
  msgType = 12;
  uuid = 0;
}

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

利用objc的runtime来定位次线程中unrecognized selector sent to instance的问题,布布扣,bubuko.com

时间: 2024-12-17 14:46:22

利用objc的runtime来定位次线程中unrecognized selector sent to instance的问题的相关文章

UNRECOGNIZED SELECTOR SENT TO INSTANCE 问题快速定位的方法

开发中常见的一类崩溃错误是遇到:unrecognized selector sent to instance 0xaxxxx…而backtrace又无法明确说明错误在哪行代码,如何快速定位BUG呢? 有时读代码一下很难找到是哪个instance出的问题,这时定制有效的DEBUG断点是最好的办法,方法如下: 在Debug菜单中选择 Breakpoints -> Create Symbolic Breakpoint… 在Symbol中填写如下方法签名: 1 -[NSObject(NSObject)

unrecognized selector sent to instance的定位

造成unrecognized selector sent to instance的原因很多,对象被提前release.引用的实例的方法不存在了或者这个方法在类中没有实现等等.控制台仅是简单的告诉你出现此种错误,随后一堆堆堆栈相关的地址. 这时想快速定位BUG的话,定制DEBUG断点是一个相比较好的办法,因为更好的我还没想到. OK,废话少扯,步骤如下: 1.在Xcode的菜单栏中选择Debug->Breakpoints->Create Symbolic Breakpoint点击后,会添加一个断

Handler详解系列(四)——利用Handler在主线程与子线程之间互发消息

MainActivity如下: package cc.c; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.widget.TextView; /** * Demo描述: * * 示例步骤如下: * 1 子线程给子线程本身发送消息 * 2 收到1的消

Handler具体解释系列(四)——利用Handler在主线程与子线程之间互发消息

MainActivity例如以下: package cc.c; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.widget.TextView; /** * Demo描写叙述: * * 演示样例过程例如以下: * 1 子线程给子线程本身发送消息 *

在不开启事件循环的线程中使用QTimer(QThread::run函数自带事件循环,在构造函数里创建线程,是一种很有意思的线程用法) good

引入 QTimer是Qt自带的定时器类,QTimer运行时是依赖于事件循环的,简单来说,在一个不开启事件循环(未调用exec() )的线程中,QTimer是无法使用的.通过分析Qt源码可发现,调用QTimer::start()后仅仅是在系统的定时器向量表中添加了一个定时器对象,但定时器并没有真正开启.定时器的开启需要通过processEvent()开始的一系列调用后才会真正得开启,这个过程中会处理定时器向量表中所有的定时器对象.那么实际exec()中也是在不断地调用processEvent()方

捕获线程中的异常

由于线程的本质特性,使得你不能捕获从线程中逃逸的异常.一旦异常逃出任务的run()方法它就会向外传播到控制台,除非你采取特殊的步骤捕获这种错误的异常.在Java SE5之前,你可以使用线程组来捕捉这种异常,但是有了Java SE5,就可以用Executor来解决这个问题了. 下面的任务总是会抛出一个异常,该异常会传播到其run()方法的外部,并且main()展示了当你运行它时所发生的事情: import java.util.concurrent.ExecutorService; import j

Java(多)线程中注入Spring的Bean

问题说明 今天在web应用中用到了Java多线程的技术来并发处理一些业务,但在执行时一直会报NullPointerException的错误,问题定位了一下发现是线程中的Spring bean没有被注入,bean对象的值为null. 原因分析 web容器在启动应用时,并没有提前将线程中的bean注入(在线程启动前,web容易也是无法感知的) 解决方案 方法有多种,网上也看到了不少. 1. 使用static声明变量 可参见 引用 http://blog.csdn.net/bjamosgavin/ar

Swing中耗时任务需要另起新线程,这个新线程中更新GUI的操作仍需由EDT来做(转)

最近调试程序时发现,点击某个界面时会出现卡死的情况,出现的频率还是比较频繁的. 再次出现卡死的情况后,利用jvisualvm查看线程的运行情况,dump操作之后发现线程间出现了死锁:Found one Java-level deadlock:============================="Thread-122":  waiting to lock monitor 0x484052e4 (object 0x1af2bb08, a com.raisecom.ems.temple

浅谈C#中Control的Invoke与BeginInvoke在主副线程中的执行顺序和区别

今天无意中看到有关Invoke和BeginInvoke的一些资料,不太清楚它们之间的区别.所以花了点时间研究了下. 据msdn中介绍,它们最大的区别就是BeginInvoke属于异步执行的. Control.Invoke 方法 (Delegate) :在拥有此控件的基础窗口句柄的线程上执行指定的委托. Control.BeginInvoke 方法 (Delegate) :在创建控件的基础句柄所在线程上异步执行指定委托. msdn说明: 控件上的大多数方法只能从创建控件的线程调用. 如果已经创建控