美文网首页iOS Developer
聊聊一个关于UIAlertController的“熊猫”Bug

聊聊一个关于UIAlertController的“熊猫”Bug

作者: 皮皮Warrior | 来源:发表于2017-01-08 19:33 被阅读415次

    “熊猫”Bug是指一种极难出现、难以摸索原因的bug。


    今天产品在众测平台上发现了一个“熊猫”bug,这个bug的表现很奇怪,弹出的UIAlertController的按钮没有文字,但title和message有文字。这个bug我们无法重现,在外网只有两个用户的手机上出现,出现之后就是必现的。

    alertActionSheet alertView

    这个bug刚拿到手有点茫然,不明白为什么使用系统的弹窗会有这种问题,一度怀疑是苹果的Bug。

    建立猜想

    由于这个bug只有外网的两个用户能重现,所以初步的方案是先补一些log, 增加一些实验的代码,再打个包让用户去试,看看能否查得原因。
    由这个bug的变现,初步猜想这个bug可能成因:

    1. 没有在主线程弹出alertController(看用户的log排除了)
    2. 按钮的字是白色的(这个自己实验就排除了)
    3. 弹出时有其他动画冲突,导致没有渲染,需要手动调用setNeedsDisplay
    4. 按钮layout时出了问题
    5. 系统的bug(bug只在Qzone出现)
    6. 需要手动设一下tintColor

    根据以上的猜想,总结出了3个解决方案,打包给用户实验:

    1. 弹出前手动设一下tintColor
    2. 弹出前手动调用setNeedsDisplay
    3. 什么也不做,排除环境影响

    同时在弹出时,把alertController的UI层级打印出来,判断是不是layout除了问题。

    补log,打包

    增加三个按钮,让用户点击后呼出弹窗,来判断上面的3种解决方案的效果:

    //点击按钮时
    - (IBAction)tapBugfixButton:(id)sender{
        self.bugfixType = [(UIButton*)sender tag];
    }
    //呼出alertController
    - (void)showAlert{
        //alert测试包
        if (self.bugfixType == 1001) {
        //之前看log alertController.view.tintColor是有值的,所以再设置一遍试试
            alertController.view.tintColor = alertController.view.tintColor;
            QZLVLOG_INFO(@"************bugfix56118199 alertView set tintColor ************");
        }else if (self.bugfixType == 1002){
            [alertController.view setNeedsDisplay];
            QZLVLOG_INFO(@"************bugfix56118199 alertView setNeedsDisplay ************");
        }else if (self.bugfixType == 1003){
            QZLVLOG_INFO(@"************bugfix56118199 alertView do nothing ************");
        }
        self.alertCtrl = alertController;
        [self performSelector:@selector(needDescribeUI) withObject:nil afterDelay:0];
    }
    //打印UI层级
    - (void)needDescribeUI{
        NSString *description = [self.alertCtrl.view performSelector:@selector(recursiveDescription)];
        QZLVLOG_INFO(@"**********bugfix56118199 describe UI architecture**********\n%@\n",description);
    }
    

    打包给用户后,用户反馈,只有第一种方案有效,但为什么setTintColor方法才会渲染按钮?alertController.view.tintColor本身不就有值吗?
    再查看用户的alertView的UI层级log,对比正常的alertView,发现一处异常:

    //出bug的alertView层级
    <_UIAlertControllerView: 0x11357c270; frame = (10 378.5; 300 179.5); tintColor = UIExtendedSRGBColorSpace 0 0.25098 0.866667 1; layer = <CALayer: 0x174a3dbc0>>
    ……
                                        <_UIAlertControllerActionView: 0x11422e010; frame = (0 0; 300 57); Action = <UIAlertAction: 0x17030c600 Title = "�照" Descriptive = "(null)" Image = 0x0>>
    |    |    |    |    |    |    |    |    | <UIView: 0x114237800; frame = (129.5 0; 41 57); userInteractionEnabled = NO; layer = <CALayer: 0x170c34500>>
    |    |    |    |    |    |    |    |    |    | <UILabel: 0x11422c5d0; frame = (0 16.5; 41 24); text = '�照'; userInteractionEnabled = NO; tintColor = UIExtendedSRGBColorSpace 0 0.200784 0.693333 1; layer = <_UILabelLayer: 0x170484e20>>
    
    //正常的alertView层级
    <_UIAlertControllerView: 0x11357f2e0; frame = (10 378.5; 300 179.5); layer = <CALayer: 0x174c2da80>>
                                            <_UIAlertControllerActionView: 0x11357d8e0; frame = (0 0; 300 57); Action = <UIAlertAction: 0x174315600 Title = "�照" Descriptive = "(null)" Image = 0x0>>
       |    |    |    |    |    |    |    |    | <UIView: 0x11358b2c0; frame = (129.5 0; 41 57); userInteractionEnabled = NO; layer = <CALayer: 0x174a21880>>
       |    |    |    |    |    |    |    |    |    | <UILabel: 0x11358b460; frame = (0 16.5; 41 24); text = '�照'; userInteractionEnabled = NO; tintColor = UIExtendedSRGBColorSpace 0 0 0 0; layer = <_UILabelLayer: 0x17448cfd0>>
    

    可以看到tintColor = UIExtendedSRGBColorSpace 0 0 0 0,alertView的按钮Label的tintColor是RGBA(0,0,0,0),alpha值是0,所以才没有显示。原来alertController.view.tintColoralertController.alertControllerActionView.label.tintColor不是一个值!这就解释了alertController.view.tintColor有值但按钮不显示的现象。

    确认猜想,查原因

    现在确认了是tintColor的原因,接下来就开始查alertControllerActionView.label.tintColor在哪被改变了,可是alertControllerActionView是系统控件的私有接口,无法获取,更别说改变了。
    一筹莫展的时候,看到了这个bug报告,其中说道:

    When an application window's tintColor is set to white, any UIAlertController launched bears the same tintColor even when the instance of the UIAlertContoller is set to something else

    由这个线索追查到,有问题的alertView都是在一个新建的UIWindow被弹出,弹出代码如下:

    //新建alertController
    //……
    
    //新建一个Window
    - (UIWindow *)alertWindow
    {
        if (!_alertWindow) {
            _alertWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
            _alertWindow.rootViewController = [QZLVForbidAutoRotateController new];
            _alertWindow.windowLevel = NSUIntegerMax;
            _alertWindow.hidden = NO;
            _alertWindow.userInteractionEnabled = YES;
            _alertWindow.tintColor = [[UIWindow valueForKey:@"keyWindow"] tintColor];
        }
        
        return _alertWindow;
    }
    
    //弹出alertController
    [[self alertWindow].rootViewController presentViewController:alertController animated:YES completion:nil];
    

    在本地将_alertWindow.tintColor设为RGBA(0,0,0,0)后,复现了这个bug。同时再一次补log打包给用户,确认了这个原因。

    alertActionSheetalertActionSheet alertViewalertView

    原来是在代码的其他地方,修改了keyWindow的tintColor为RGBA(0,0,0,0),导致了_alertWindow.tintColor被赋值为了透明色。而苹果的默认实现中,将alertController的按钮Label(alertControllerActionView.label.tintColor)和弹出它的window.tintColor相等,导致了这个bug。

    总结

    看来苹果关于UIAlertController的实现是有坑的,除非手动设置alertController.view.tintColor,否则alertControllerActionView.label.tintColor与presenter的window.tintColor是相关联的,让人有点摸不到头脑。不过测试了同样的代码,使用UIAlertView就不会有这个bug。
    以后使用UIAlertController时,这一点要多多注意了。
    这次这个bug时间比较紧急,leader、导师、同事们给了我很多建议和指导,看来查bug方法论还是很重要的。

    相关文章

      网友评论

        本文标题:聊聊一个关于UIAlertController的“熊猫”Bug

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