iOS程序猿iOS学习笔记IOS

1、Crash产生原因

2019-01-28  本文已影响21人  SunshineBrother

Crash我们不得不面对的问题,但是好多人在遇到Crash的时候都无从下手,很多的时候都是凭着感觉找问题。今天我做了5篇文章来帮助我们更加清晰的认清iOS中的Crash

想要了解更详细的内容可以点击这里

Crash分类

一般是由 Mach异常或 Objective-C 异常(NSException)引起的。我们可以针对这两种情况抓取对应的 Crash 事件

crash2.png

Mach异常

Mach操作系统微内核,是许多新操作系统的设计基础。Mach微内核中有几个基础概念:

Mach 异常是指最底层的内核级异常,被定义在 <mach/exception_types.h>下。mach异常由处理器陷阱引发,在异常发生后会被异常处理程序转换成Mach消息,接着依次投递到thread、task和host端口。如果没有一个端口处理这个异常并返回KERN_SUCCESS,那么应用将被终止。每个端口拥有一个异常端口数组,系统暴露了后缀为_set_exception_ports的多个API让我们注册对应的异常处理到端口中

Mach异常方式

crash.png

Mach提供少量API

// 内核中创建一个消息队列,获取对应的port
mach_port_allocate();
// 授予task对port的指定权限
mach_port_insert_right();
// 通过设定参数:MACH_RSV_MSG/MACH_SEND_MSG用于接收/发送mach message
mach_msg();

Mach异常捕获
task_set_exception_ports(),设置内核接收Mach异常消息的Port,替换为自定义的Port后,即可捕获程序执行过程中产生的异常消息。

+ (void)createAndSetExceptionPort {
mach_port_t server_port;
kern_return_t kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &server_port);
assert(kr == KERN_SUCCESS);
NSLog(@"create a port: %d", server_port);

kr = mach_port_insert_right(mach_task_self(), server_port, server_port, MACH_MSG_TYPE_MAKE_SEND);
assert(kr == KERN_SUCCESS);

kr = task_set_exception_ports(mach_task_self(), EXC_MASK_BAD_ACCESS | EXC_MASK_CRASH, server_port, EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE);

[self setMachPortListener:server_port];
}

// 构造BAD MEM ACCESS Crash
- (void)makeCrash {
NSLog(@"********** Make a [BAD MEM ACCESS] now. **********");
*((int *)(0x1234)) = 122;
}

以上代码参考iOS Mach异常和signal信号

mach异常即便注册了对应的处理,也不会导致影响原有的投递流程。此外,即便不去注册mach异常的处理,最终经过一系列的处理,mach异常会被转换成对应的UNIX信号,一种mach异常对应了一个或者多个信号类型。因此在捕获crash要提防二次采集的可能

crash3.png

处理signal

当错误发生时候,先在最底层产生Mach异常;Mach异常在host层被转换为相应的Unix Signal; 在OC层如果有对应的NSException(OC异常),就转换成OC异常,OC异常可以在OC层得到处理;如果OC异常一直得不到处理,程序会强行发送SIGABRT信号中断程序。在OC层如果没有对应的NSException,就只能让Unix标准的signal机制来处理了

signal.h中声明了32种异常信号,常见的有以下几种

应用
1.AppDelegate.m

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.

InstallSignalHandler();//信号量截断
InstallUncaughtExceptionHandler();//系统异常捕获

return YES;
}

2.SignalHandler.m的实现

void SignalExceptionHandler(int signal)
{
NSMutableString *mstr = [[NSMutableString alloc] init];
[mstr appendString:@"Stack:\n"];
void* callstack[128];
int i, frames = backtrace(callstack, 128);
char** strs = backtrace_symbols(callstack, frames);
for (i = 0; i <frames; ++i) {
[mstr appendFormat:@"%s\n", strs[i]];
}
[SignalHandler saveCreash:mstr];

}

void InstallSignalHandler(void)
{
signal(SIGHUP, SignalExceptionHandler);
signal(SIGINT, SignalExceptionHandler);
signal(SIGQUIT, SignalExceptionHandler);

signal(SIGABRT, SignalExceptionHandler);
signal(SIGILL, SignalExceptionHandler);
signal(SIGSEGV, SignalExceptionHandler);
signal(SIGFPE, SignalExceptionHandler);
signal(SIGBUS, SignalExceptionHandler);
signal(SIGPIPE, SignalExceptionHandler);
}

有关错误类型可以看上面的说明,SignalExceptionHandler是信号出错时候的回调。当有信号出错的时候,可以回调到这个方法

3.UncaughtExceptionHandler.m的实现

void HandleException(NSException *exception)
{
// 异常的堆栈信息
NSArray *stackArray = [exception callStackSymbols];
// 出现异常的原因
NSString *reason = [exception reason];
// 异常名称
NSString *name = [exception name];
NSString *exceptionInfo = [NSString stringWithFormat:@"Exception reason:%@\nException name:%@\nException stack:%@",name, reason, stackArray];
NSLog(@"%@", exceptionInfo);
[UncaughtExceptionHandler saveCreash:exceptionInfo];
}

void InstallUncaughtExceptionHandler(void)
{
NSSetUncaughtExceptionHandler(&HandleException);
}

代码参考至向晨宇的技术博客-iOS异常捕获

demo地址

NSException异常

常见的NSException异常有

更加详细的信息请参考Baymax:网易iOS App运行时Crash自动防护实践

unrecognized selector类型

unrecognized selector类型的crash在app众多的crash类型中占着比较大的成分,通常是因为一个对象调用了一个不属于它方法的方法导致的。

方法调用流程
runtime中具体的方法调用流程大致如下:

在一个函数找不到时,runtime提供了三种方式去补救:

通过重写NSObject的forwardingTargetForSelector方法,我们就可以将无法识别的方法进行拦截并且将消息转发到安全的桩类对象中,从而可以使app继续正常运行

KVO crash 产生原因

KVO,即:Key-Value Observing,它提供一种机制,当指定的对象的属性被修改后,则对象就会接受收到通知。简单的说就是每次指定的被观察的对象的属性被修改后,KVO就会自动通知相应的观察者了。

KVO机制在iOS的很多开发场景中都会被使用到。不过如果一不小心使用不当的话,会导致大量的crash问题

通过会导致KVO Crash的两种情形

解决方法:可以让被观察对象持有一个KVO的delegate,所有和KVO相关的操作均通过delegate来进行管理,delegate通过建立一张map来维护KVO整个关系。具体就是使用runTime的交换方法重写KVO的一些方法

NSNotification类型crash防护

当一个对象添加了notification之后,如果dealloc的时候,仍然持有notification,就会出现NSNotification类型的crash。
NSNotification类型的crash多产生于程序员写代码时候犯疏忽,在NSNotificationCenter添加一个对象为observer之后,忘记了在对象dealloc的时候移除它。
所幸的是,苹果在iOS9之后专门针对于这种情况做了处理,所以在iOS9之后,即使开发者没有移除observer,Notification crash也不会再产生了。
不过针对于iOS9之前的用户,我们还是有必要做一下NSNotification Crash的防护的。

NSNotification Crash的防护原理很简单, 利用method swizzling hook NSObject的dealloc函数,再对象真正dealloc之前先调用一下[[NSNotificationCenter defaultCenter] removeObserver:self]即可。

NSTimer类型crash防护

在程序开发过程中,大家会经常使用定时任务,但使用NSTimer的 scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:接口做重复性的定时任务时存在一个问题:NSTimer会强引用target实例,所以需要在合适的时机invalidate定时器,否则就会由于定时器timer强引用target的关系导致target不能被释放,造成内存泄露,甚至在定时任务触发时导致crash。 crash的展现形式和具体的target执行的selector有关。

与此同时,如果NSTimer是无限重复的执行一个任务的话,也有可能导致target的selector一直被重复调用且处于无效状态,对app的CPU,内存等性能方面均是没有必要的浪费。

那么解决NSTimer的问题的关键点在于以下两点:

Container crash 防护方案

Container crash 类型的防护方案也比较简单,针对于NSArray/NSMutableArray/NSDictionary/NSMutableDictionary/NSCache的一些常用的会导致崩溃的API进行method swizzling,然后在swizzle的新方法中加入一些条件限制和判断,从而让这些API变的安全

野指针crash 防护方案

野指针问题的解决思路方向其实很容易确定,XCode提供了Zombie的机制来排查野指针的问题,那么我们这边可以实现一个类似于Zombie的机制,加上对zombie实例的全部方法拦截机制 和 消息转发机制,那么就可以做到在野指针访问时不Crash而只是crash时相关的信息。
同时还需要注意一点:因为zombie的机制需要在对象释放时保留其指针和相关内存占用,随着app的进行,越来越多的对象被创建和释放,这会导致内存占用越来越大,这样显然对于一个正常运行的app的性能有影响。所以需要一个合适的zombie对象释放机制,确定zombie机制对内存的影响是有限度的

非主线程刷UI类型crash防护

在非主线程刷UI将会导致app运行crash,有必要对其进行处理。
目前初步的处理方案是swizzle UIView类的以下三个方法:

- (void)setNeedsLayout;
- (void)setNeedsDisplay;
- (void)setNeedsDisplayInRect:(CGRect)rect;

在这三个方法调用的时候判断一下当前的线程,如果不是主线程的话,直接利用 dispatch_async(dispatch_get_main_queue(), ^{ //调用原本方法 });
来将对应的刷UI的操作转移到主线程上,同时统计错误信息。
但是真正实施了之后,发现这三个方法并不能完全覆盖UIView相关的所有刷UI到操作,但是如果要将全部到UIView的刷UI的方法统计起来并且swizzle,感觉略笨拙而且不高效。

漫谈 iOS Crash 收集框架

全面的理解和分析iOS的崩溃日志

iOS实录14:浅谈iOS Crash(一)

质量监控-保护你的crash

深入iOS系统底层之crash解决方法介绍

Baymax:网易iOS App运行时Crash自动防护实践

上一篇 下一篇

猜你喜欢

热点阅读