美文网首页iOS开发
iOS开发——__ZL17_WebTryThreadLockb

iOS开发——__ZL17_WebTryThreadLockb

作者: coderanger | 来源:发表于2017-08-06 23:10 被阅读214次

    sdk 中涉及到 UI 操作的时候,一定要注意线程问题!一定要注意线程问题!一定要注意线程问题!

    从最初开始学习 iOS 的时候,我们就被告知 UI 操作一定要放在主线程进行,这是因为 UIKit 的方法不是线程安全的,保证线程安全需要极大的开销。

    试想一下几种场景:

    • 两个线程同时设置同一个背景图片,则很有可能因为当前图片被释放了两次而导致应用崩溃;
    • 两个线程同时设置同一个 UIView 的背景色,则很有可能渲染显示的是颜色 A,而此时在 UIView 逻辑树上的背景颜色属性为 B;
    • 两个线程同时操作 View 的树形结构:在线程 A 中 for 循环遍历并操作当前 View 的所有 subView,而此时线程 B 中将某个 subView 直接删除,这就导致了错乱,甚至导致应用崩溃。

    出了什么问题?

    最近 hybrid sdk 收尾,偶然发现线上有一类 crash 最近两个版本稳步上升,而且可以肯定的是我负责的 sdk 提供的 web 容器导致的。__ZL17_WebTryThreadLockb 是函数调用栈最后调用的 api,第一次看到的时候,咦,这是什么鬼?原谅我见的世面少,经过一顿 Stack Overflow,翻阅了好几个相关的问题,总结起来就是在子线程执行了 UI 线程的操作。

    crash 线程详细如下:

    Thread 17 Crashed:
    0   WebCore                              __ZL17_WebTryThreadLockb
    1   WebCore                              _WebThreadLock
    2   UIKit                                -[UIWebBrowserView resignFirstResponder]
    3   UIKit                                -[UIResponder _resignIfContainsFirstResponder]
    4   UIKit                                -[UIView(Hierarchy) _willMoveToWindow:]
    5   UIKit                                -[UIView(Internal) _addSubview:positioned:relativeTo:]
    6   UIKit                                ___53-[_UINavigationParallaxTransition animateTransition:]_block_invoke_2
    7   UIKit                                +[UIView(Animation) performWithoutAnimation:]
    8   UIKit                                ___53-[_UINavigationParallaxTransition animateTransition:]_block_invoke
    9   UIKit                                +[UIView(Internal) _performBlockDelayingTriggeringResponderEvents:]
    10  UIKit                                -[_UINavigationParallaxTransition animateTransition:]
    11  UIKit                                -[UINavigationController _startCustomTransition:]
    12  UIKit                                -[UINavigationController _startDeferredTransitionIfNeeded:]
    13  UIKit                                -[UINavigationController __viewWillLayoutSubviews]
    14  UIKit                                -[UILayoutContainerView layoutSubviews]
    15  UIKit                                -[UIView(CALayerDelegate) layoutSublayersOfLayer:]
    16  QuartzCore                           -[CALayer layoutSublayers]
    17  QuartzCore                           __ZN2CA5Layer16layout_if_neededEPNS_11TransactionE
    18  QuartzCore                           __ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE
    19  QuartzCore                           __ZN2CA7Context18commit_transactionEPNS_11TransactionE
    20  QuartzCore                           __ZN2CA11Transaction6commitEv
    21  QuartzCore                           __ZN2CA11Transaction14release_threadEPv
    22  libsystem_pthread.dylib              __pthread_tsd_cleanup
    23  libsystem_pthread.dylib              __pthread_exit
    24  libsystem_pthread.dylib              __pthread_wqthread
    25  libsystem_pthread.dylib              _start_wqthread
    

    原因是什么?

    Stack Overflow 上的问题和解答贴出两个大家一起参考:
    app-crash-no-idea-why
    ios-webtrythreadlock-crash
    既然确定了是因为在子线程执行了 UI 操作导致,那么,我的 sdk 里哪里发生了这一类调用呢?先回顾了下 sdk 的所有关键模块,排除了核心部分,那些一定不会涉及到 ui 操作的模块后,就剩下 webview模块、bridge 模块和 debug 模块了。

    那就继续 review 吧,一个一个来~ webview 检查了一遍都是在主线程执行的 ui 操作,应该不会出问题;debug 模块,刚开始写的时候打 log 部分确实没有考虑线程操作问题,大师兄已经帮我 review 出来并修复掉了,review 了两边也没发现有问题,应该也不会是 debug 模块导致的。那就剩 bridge 模块了~~~

    艾玛,最头疼的一部分,因为 bridge 接口是提供给前端 h5 页面和 native 模块进行交互的 api,包括了公共 bridge,还包括 业务线自定义的 bridge 接口,太多了
    ,着实头疼~~~没办法,硬着头皮找大师兄帮忙,看看能不能缩短问题定位的周期。

    大师兄毕竟大师兄!很多让我查一下分享 api、通讯录 api、照片库 api、页面
    跳转 api,哎哟我去!这一下就缩小了范围!当然,其实这四类模块 bridge 接口也是一大群 api 啊!必须再进一步缩小范围。

    公司的 crash 日志平台里包含一个 crash 现场的功能,以前没用过,这两天刚发现的,别说,还真好用,crash 现场记录了用户在客户端发生 crash 的前后都在做什么,比如哪个页面?比如调用了哪些接口?前后用户是怎么操作的?用户当前设备的系统信息?用户信息?等等。

    很快,比对了几个同类 crash 的现场后,均发现一个现象,都是在某个业务页面中发生的,并且那个页面就是用的 sdk 提供的 web 容器!巧了!看来就是这里了!联系了下这个页面的开发同学都调用了哪些 bridge 方法?其实自己在 debug 的时候也可以通过断点拿到,但是,防止由于业务逻辑问题错误一些漏掉的,最好还是咨询下相关业务开发。两边一对比,ok,接口一致了。

    很快,定位到了通讯录授权回调的部分确实有子线程执行 UI 线程操作的调用,代码如下:

    ABAuthorizationStatus authStatus = ABAddressBookGetAuthorizationStatus();
        if (authStatus == kABAuthorizationStatusNotDetermined)
        {
            //获取授权
            ABAddressBookRef addressBook = ABAddressBookCreate();
            ABAddressBookRequestAccessWithCompletion( addressBook, ^(bool granted, CFErrorRef error) {
                if (granted)
                {
                    [self openContact];
                }
                else
                {
                    [self showAlertView];
                }
            });
        }
    

    唉,这段代码确实问题大啊!虽然丢人,但是一为了警醒自己,二为了给大家也都指出下边今后犯同样的问题,这里贴出来大家看一看。

    这里的问题至少有两个:

    1. 子线程执行 UI 线程操作;
    2. 内存泄漏

    如何解决?

    问题的原因找到了,怎么办呢?线上已经发版了,而且大师兄还提醒,从上个版本就有这类 crash 啦!

    额,肿么办,肿么办~~~

    不能慌,以后还会经历很多类似的线上问题,不能慌!冷静冷静~对了,patch,发个 patch 修复吧!好,patch,也就能期望 patch 可以解决了。不 patch 的话,老大看到这一类蹭蹭蹭往上涨的 crash,不得开了我!不 patch 的话,就得下个版本修复了,用户体验多不好!不 patch 的话,根据墨菲定律,可能发生的事就一定会发生!这个偶现的 crash 线上一定会发生!

    正确的写法:

    ABAuthorizationStatus authStatus = ABAddressBookGetAuthorizationStatus();
        if (authStatus == kABAuthorizationStatusNotDetermined)
        {
            //获取授权
            ABAddressBookRef addressBook = ABAddressBookCreate();
            ABAddressBookRequestAccessWithCompletion( addressBook, ^(bool granted, CFErrorRef error) {
                __weak typeof(self) weakSelf = self;
                dispatch_async(dispatch_get_main_queue(), ^{
                    if (granted)
                    {
                        [weakSelf openContact];
                    }
                    else
                    {
                        [weakSelf showAlertView];
                    }
                });
            });
        }
    

    现在,问题来了,这写法能 patch 吗?不敢说,这种多线程相关的还真不确定能 patch 搞定,没办法,咨询吧!

    需要确认的部分:

    1.待 patch 的方法调用了 C 函数;
    2.dispatch 函数;
    3. weak 指针;
    4. C 函数传递 block 参数;
    

    联系了下,得到的结论是:都支持!

    欧耶!开干吧~修复代码部分、自测、准备 patch、申请 patch、自测、提交 QA 测试、灰度放量~

    ok!

    相关文章

      网友评论

      • 网密:addressBook泄漏了。
        weakSelf写的没有意义。。。

      本文标题:iOS开发——__ZL17_WebTryThreadLockb

      本文链接:https://www.haomeiwen.com/subject/wcwnlxtx.html