bugly怎么读_腾讯Bugly巨坑:使用不当造成UI界面卡死

本文由CocoaChina网友gagaga投稿

前言

Bugly和dispatch_once使用不当,会造成UI界面卡死。笔者在前段时间碰见了这样的一个卡死的情况,特意记录下来。

iOS开发者或多或少都听过或用过Bugly。它是腾讯开发的一个SDK,用来捕捉App中的crash。对于dispatch_once大家就更熟悉了,现在大部分开发者用这个来创建单例。如:+ (SingletonA *)sharedInstance {

static SingletonA *_singleton = nil;

static dispatch_once_t once;

dispatch_once(&once, ^{

_singleton = [[SingletonA alloc] init];

});

return _singleton;

}

但是这两个在一起怎么会造成UI界面卡死呢?如果笔者不是亲眼所见,也不会相信Bugly会造成界面卡死。

现象

前几天碰见了这样一个情况,我们的App启动时有时候会卡在启动界面上,过一段时间 就会被系统杀掉,而且不会有Crash的堆栈。这个现象让我们开发很头疼,一旦出现就只能杀进程,重新启动App,并且还不知道是怎么回事。

调查

看到界面卡死的第一反应就是,是不是哪个地方死锁导致主线程阻塞了。使用Console.app查看App启动时的日志,没发现什么异常的情况,并且死锁这个在日志中查找起来比较麻烦。

好不容易复现这个情况后,赶紧把手机接上Mac,在Xcdoe中Attach我们App的进程,如图:

然后暂停下App进程,就可以看到当前所有线程的堆栈情况了。如图:

这下,我们才知道是卡在了dispatch_once这个地方。是我们的单例使用有问题吗?我们知道, 如果dispatch_once递归调用就会产生死锁。示例代码如下:+ (SingletonA *)sharedInstance {

static SingletonA *_singleton = nil;

static dispatch_once_t once;

dispatch_once(&once, ^{

_singleton = [[SingletonA alloc] init];

});

return _singleton;

}

- (instancetype)init {

self = [super init];

if (self) {

[self somethingInit];

// 这个方法里也调用了[SingletonA sharedInstance],所以会产生死锁

}

return self;

}

4

- (void)somethingInit {

[SingletonA sharedInstance];

}

很有可能就是这个原因导致我们的App启动时卡死。于是我们开始排查dispatch_once的代码里会不会在某个条件下再次调用到相同的dispatch_once,形成递归调用,导致死锁。

就这样折腾了好久,也没有发现dispatch_once形成递归调用的可能性,就在调查快陷入僵局的时候,笔者在日志中发现了一些信息:*** Assertion failure in -[XXXXXManager XXXXXmethod], /Users/whf/Desktop/WHF/XXXXX/XXXXX/XXXXXManager.m:32

怎么会有NSAssert断言日志信息,有NSAssert断言时App不应该早就Crash了吗,为什么会卡住被系统结束运行?笔者把NSAssert断言的地方做了处理,不会再触发断言了,然后重新Debug安装App后试了很多次,果然就不会再卡住了。

到此,启动卡住的触发原因找到了,是因为dispatch_once中的代码有NSAssert断言的Crash,导致主线程卡住。

但是,为什么会这样,笔者是一个喜欢刨根问底的人,于是有了下面的问题复现。

问题复现dispatch_once中执行的代码有Crash的话,会造成死锁吗?难道是NSAssert使用的@try{}@catch{}的异常机制改变了代码的执行顺序,致使dispatch_once死锁?

这个解释太牵强,感觉站不住脚。

笔者新建了一个demo工程,来测试这种情况。代码大致如下:

SingletonA.m 文件:@implementation SingletonA

+ (SingletonA *)sharedInstance {

static SingletonA *_singleton = nil;

static dispatch_once_t once;

dispatch_once(&once, ^{

_singleton = [[SingletonA alloc] init];

});

return _singleton;

}

- (instancetype)init {

self = [super init];

if (self) {

[self somethingInit];

}

return self;

}

- (void)somethingInit {

NSAssert(NO, @"not support message type");

}

@end

使用这个单例的地方MainViewController.m :@implementation MainViewController

- (void)viewDidLoad {

[super viewDidLoad];

[SingletonA sharedInstance];

}

@end

代码跑起来后,并没有死锁。难道是需要多线程访问[SingletonA sharedInstance]这个方法?

立马修改了下使用这个单例的地方- (void)viewDidLoad {

[super viewDidLoad];

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

[SingletonA sharedInstance];

});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

[SingletonA sharedInstance];

});

[SingletonA sharedInstance];

}

在全局队列里也访问了这个单例,然而,并没有出现死锁的情况。 太令人失望了,看来,在dispatch_once执行的代码中有crash,并不会造成dispatch_once死锁。

正当陷入僵局的时候,再次看了下当时我们App卡死时后的堆栈。如图:

发现了Bugly的身影,笔者立刻在Demo工程中加入了Bugly库,然后重新跑了下代码,还是没有死锁啊,怎么回事? 又仔细端详了上面这张图,这次有新的发现。Bugly是在非主线程捕获到的这次Crash,而主线程也访问了这个出问题的单例,会不会是因为后台线程初始化这个单例的时候crash了,Bugly捕获了这个Crash在这个后台线程处理,然后主线程访问这个单例就在一直等待这个单例初始化完成。好了,笔者改了下调用单例的地方,让主线程休眠1s,方便后台线程提前初始化单例。- (void)viewDidLoad {

[super viewDidLoad];

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

[SingletonA sharedInstance];

});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

[SingletonA sharedInstance];

});

sleep(1); // 让后台线程先去初始化SingletonA

[SingletonA sharedInstance];

}

果然,死锁了!!!

主线程截图:

后台线程截图:

现在明白了,使用dispatch_once的单例初始化的时候抛出异常或者Crash时,Bugly捕获到后进行处理,这时候如果主线程再试图去访问这个单例,就会造成死锁。

总结

Bugly和dispatch_once在以下条件下造成会死锁:后台线程初始化dispatch_once单例时抛出异常或者crash,

主线程也正在访问这个单例。

为什么说这个是Bugly的巨坑呢?

这种情况下正常的流程就应该Crash了,但是Bugly把这种错误变为了死锁,掩盖了问题,最后被系统杀掉,没有了Crash堆栈。这样就不好查找定位问题了,尤其是当这个Crash不是必现,而且还是线上版本的时候,就更不容易排查问题了。Bugly把我们活生生地往深沟里带,让我们最开始排查问题的方向就错了。

所以,如果集成了Bugly,App又经常不明情况的卡死可以排查下这类情况。

如何避免?

虽说是Bugly造成的死锁,但其根本原因还是我们自己代码Crash了,Bugly只是掩盖住了这种Crash,让我们不容易排查问题。那要如何避免呢?项目少用单例。笔者一直不太喜欢什么东西都搞个单例来处理。

dispatch_once中的代码尽量只做初始化的事情,不要调用很多其他的方法。

dispatch_once中的代码尽量不要抛出异常,不要Crash。

给Bugly报Bug。

好了,总结完了,成功把自己代码的问题甩锅给Bugly~~~~

请大家轻拍~~~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值