在ios开发的世界里,通过动画来切换界面使我们早就习以为常的事情,但动画将一个原本同步执行的事务,变成一个异步事务,并由此引发了一系列的陷阱。
最近对公司产品的crashlytics报告进行了一些分析,发现这类bug在各个产品里都占据较高比例,因此总结了一下常见的case。
present ViewController引发的混乱
present一个controller的时候,如果一个present的动画正在执行过程当中,程序会抛出异常,异常告诉你Attempting to begin a modal transition from * to * while a transition is already in progress. Wait for viewDidAppear/viewDidDisappear to know the current transition has completed;
解决的办法是引入一个present事务控制器,用来对present请求进行排队,这种方案是否足够可靠,还有待验证。NavigationController引发的混乱
与present类似,NavigationController的push&pop也会引发类似的错误,症状是,在多重push的时候不会立即crash,视图的内容部分会一团漆黑,但是在回退的时候会crash,异常是Fatal Exception: NSInvalidArgumentException Can't add self as subview
,从crash的调用栈看,是navigationbar内部错误。
解决的方法是可以继承UINavigationController,通过UINavigationControllerDelegate来监测状态,如果当前正在处于上一个push的执行过程当中,可以忽略掉新的push请求。UIScrollView(UITableView)的delegate引发的崩溃
虽然ios5.0开始就已经支持weak引用,但很多的cocoa Touch类的delegate属性还是assign,这说明他们保存了一个不安全的引用。UIScrollView就是这样,如果controller里面有个UIScrollView,delegate属性是自身,那么有可能发生这样的情况:uiscrollview正在进行一个动画事务(出问题的case中,动画往往不是由于用户操作导致的),用户退出了当前的controller,动画完成的时候controller可能已经被dealloc,此时会发生EXEC_BAD_ACCESS崩溃。
如果crash的的栈显示与UIScrollView有关,又表明是在Animation结束的时候,那么就很有可能是这个原因;这样的bug往往很难复现,据经验,一般出现在那些通过定时器启动动画的场合。
因此,作为一种防御性的手段,建议在controller的dealloc里面,将相关的delegate置为nil多个UI事件同时发生引发的混乱
一个同事提供的线索,如果你同时点击UI页面的多个可点击控件,UI有可能发生混乱。
比如同时点击多个button,uicontrol,绑定gesture的uiview等,只要刻意试一下我们的产品都有这个问题。
现在的解决的方案是将相关可点击view的exclusiveTouch属性设置为true,如果工程里面UI控件都是用工厂方法创建的,那么改起来还是很方便的。
不过gesture是不受这个属性的影响的,目前还没有发现如何解决,因此只能规避,如果能够使用UIControl来解决的问题,就不要使用UIView+Gesture来解决。
这种问题如果不是刻意的去操作,一般还是很少会暴露的。
上面的几点是最近对线上一类疑难bug分析之后总结出来的根本原因;其本质上都是UI事务叠加导致的。上面1和2发生的根源一般都是,定时器或后台逻辑促发的界面切换,恰好和另一个界面切换撞在了一起,因此在测试的时候可能很难发现;在用户手上复现的概率也不是特别高,但总是会出现。所以在设计应用的时候,最好要避免这种从非用户操作导致的界面切换。
如果每个UI事务都不加动画、同步完成,那么就不会有这些情况,但这是不可能的。驱动App运行的所有事件源,包括定时任务、异步任务、用户操作、外部事件(比如推送消息)等,叠加在一起,使得完全避免上述的情况几乎不可能,关键是ios的UI框架对此基本不设防,只能靠我们自己积累经验,谨慎小心。