问题:有哪些常见的 Crash 场景?

常见的 Crash 场景

  • 访问了僵尸对象
  • 访问了不存在的方法unrecognized selector sent to instance
  • 数组越界
  • 在定时器下一次回调前将定时器释放,会Crash
  • KVC造成的crash
  • EXC_BAD_ACCESS
  • KVO引起的崩溃
  • 多线程中的崩溃
  • Socket长连接,进入后台没有关闭
  • Watch Dog超时造成的crash

什么时候会报 unrecognized selector 异常

当调用对象(子类,各级父类)中不含有对应方法的时候,并且依旧没有给出“消息转发”的具体方案的时候,程序在运行时会crash并抛出 unrecognized selector 异常
OC 中的每个方法在运行时会被转为消息发送objc_msgSend(reciver, selector)
例如 [person say]就会被转化为 objc_msgSend(person, @selector(say))
运行时会根据对象(reciever) 的isa 指针找到该对象所对应的类,然后会依次在对应的 类,父类,爷爷类,根类中找对应的方法

下面讲述对象方法的解析过程:

  • 第一步:+(BOOL)resolveInstanceMethod:(SEL)sel``实现方法,指定是否动态添加方法。 若返回NO,则进入下一步,若返回YES,则通过class_addMethod`函数动态地添加方 法,消息得到处理,此流程完毕。
  • 第二步:在第一步返回的是NO时,就会进入- (id)forwardingTargetForSelector:(SEL)aSelector方法,这是运行时给我们的第二次机会,用于指定哪个对象响应这个selector。不能指定为self。若返回nil,表示没有响应 者,则会进入第三步。若返回某个对象,则会调用该对象的方法。
  • 第三步:若第二步返回的是nil,则我们首先要通过- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector指定方法签名,若返回nil,则表示不处 理。若返回方法签名,则会进入下一步。
  • 第四步:当第三步返回方法方法签名后,就会调用- (void)forwardInvocation:(NSInvocation *)anInvocation方法,我们可以通过anInvocation 对象做很多处理,比如修改实现方法,修改响应对象等
  • 第五步:若没有实现- (void)forwardInvocation:(NSInvocation *)anInvocation方法,那么 会进入- (void)doesNotRecognizeSelector:(SEL)aSelector方法。若我们没有实现这个方 法,那么就会crash,然后提示打不到响应的方法。到此,动态解析的流程就结束了。

数组越界

在数组取值时加判断

if array.count > index {
   let value = array[index]
}

KVC造成的crash

给不存在的key(包括key为nil)设置value

关于EXC_BAD_ACCESS

出现的原因: 访问了野指针, 比如访问已经释放对象的成员变量或者发消息, 死循环等;
解决方法:

  • 重写对象的respondsToSelector 方法, 先找到出现 EXECBADACCESS 前访问的最后一个 object;

  • 设置Enable Zombie Objects;
    Zombie
  • 设置全局断点快速定位问题代码所在行,接收所有异常;

  • Xcode7 之后已经集成了 BAD_ACCESS 捕获功能: Address Sanitizer 与步骤 2 一样设置;
    Address Sanitizer
  • analyze(静态分析, 不一定管用)

如何解决很难复现的crash

  • 1.有错误日志先看错误日志信息
  • 2.没有错误日志,第一步分析函数中的所有分支, 是否在语法上存在可能缺少条件的问题.所以检查所以的分支,确保每个分支执行的结果是正确的.
  • 3.检查函数的参数,保证必传参数不能为空,若为空应该抛出异常,因此用断言检查参数的正确性很重要
  • 4.检查函数中每个分支所调用的函数返回结果是正确的,其实就是递归过程(重复2,3步骤)

KVO引起的崩溃

  • 观察者是局部变量,会崩溃
  • 被观察者是局部变量,会崩溃
  • 没有实现observeValueForKeyPath:ofObject:changecontext:方法:,会崩溃
  • 重复移除观察者,会崩溃
  • 重复添加观察者,不会崩溃,但是添加多少次,一次改变就会被观察多少次

多线程中的崩溃

多线程遇到需要同步的时候,加锁,添加信号量等进行同步操作。一般多线程发生的Crash,会收到SIGSEGV信号,表明试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据。

  • 死锁、子线程中更新UI、多个线程同时释放一个对象
  • 在子线程中更新UI
  • dispatch_group crash,dispatch_group_leave的次数比dispatch_group_enter次数多
  • 多线程下非线程安全类的使用,如NSMutableArrayNSMutableDictionaryNSCache是线程安全的。
  • 数据缓存到磁盘和读取。

Socket长连接,进入后台没有关闭

当服务器close一个连接时,若client端接着发数据。根据TCP协议的规定,会收到一个RST响应,client再往这个服务器发送数据时,系统会发出一个SIGPIPE信号给进程,告诉进程这个连接已经断开了,不要再写了。而根据信号的默认处理规则,SIGPIPE信号的默认执行动作是terminate(终止、退出),所以client会退出。
长连接socket或重定向管道进入后台,没有关闭导致崩溃的解决办法:

  • 切换到后台是,关闭长连接和管道,回到前台重新创建。
  • 使用signal(SIGPIPE,SIG_IGN),将SIGPIP交给系统处理,这么做将SIGPIPE设为SIG_IGN,使客户端不执行默认操作,即不退出。

Watch Dog超时造成的crash

主线程执行耗时操作,导致主线程被卡超过一定时间。一般异常编码是0x8badf00d,表示应用发生watch dog超时而被iOS终止,通常是应用花费太多的时间无法启动、终止或者响应系统事件。
解决:主线程只负责更新UI和事件响应,将耗时操作(网络请求、数据库读写等)异步放到后台线程执行。

你可能感兴趣的:(问题:有哪些常见的 Crash 场景?)