▲点击上方“
CocoaChina
”关注即可免费学习iOS开发
本文为投稿文章
原文
如果把我们所做的UI做个简单分类,大致上可以分为列表界面和非列表界面。对于列表类UI,我们可以选择UITableView或者UICollectionView来实现。
UICollectionView出现之前,UITableView几乎是唯一的选择,这每日可见人人都用的UITableView里隐藏着容易忽视的危险。
同步VS异步
同步和异步是基础的编程概念,也是贯穿于我们日常的两种代码书写方式。理解sync和async不仅仅在于明白代码执行顺序上的差异,更重要的是理解这两种方式的差异对我们代码健壮性的影响。
同步的代码书写方式很直观,也是大部分初学者潜意识所选择的方式。我们只需要把心中的思路按部就班的转换成代码,就形成了一段同步的逻辑,比如下面一段同步代码:
同步强调的流程是:我
此刻
拥有哪些数据,
此刻
对这些数据进行一些计算,进而利用计算结果在
此刻
产生更多的行为。同步意味着在
当下
一步一步按顺序的完成逻辑。
当我们代码越写越多,手感变好之后,我们会写更多的异步代码。异步在执行的时间上和同步刚好相反,异步强调的是代码当下并不执行,而是等待未来某个时机到来之后再发生。比如下面一段异步代码:
对于_arr的修改操作,是在网络请求完成之后再执行的。
异步的好处在于表达能力更灵活更强,我们对于跨越某个时间段的流程,有了更好的表达方式。这几年很受技术圈热捧的Reactive Programming,精髓之一就在于异步。
Wild beast is dangerous,异步的缺点也很明显,由于跨域了一定的时间区域,在异步操作真正发生的时候,我们程序所依赖的状态很有可能在这一时间跨度内发生了意料之外的变化,进而导致奇奇怪怪的bug。具体到上面这段代码,很有可能在执行[_arr addObject:@(i)];的时候,self.arr已经被某处代码改为nil了。我在之前介绍
函数式编程的文章
中也提到了这点,赋值操作会随着时间的变化而危险起来,而Functional Programming恰好可以帮助我们解决状态维护在时间维度上的问题,这也是为什么异步的响应式编程总是和无状态的函数式编程结对出现,双剑合并成FRP。
我们再来看看UITableView中的同步与异步。
UITableView
标题中所说的危险之处正是在于异步。更具体点来说,是reloadData这个调用中所包含的异步操作。先来看看执行reloadData都发生了什么。
当我们reloadData的时候,我们本意是刷新UITableView,随后会进入一系列UITableViewDataSource和UITableViewDelegate的回调,其中有些是和reloadData同步发生的,有些则是异步发生的。
我们熟悉的下面两个回调是同步的:
而另一个最常使用的回调则是异步的:
经过上面的分析,我们不难UITableView的危险之处在于哪了,在于异步执行cellForRowAtIndexPath的时候,我们所依赖的状态可能会发生变化,上面代码中的_arr如果元素被修改过,极有可能发生数组越界的异常。
当列表界面数据不怎么变化的时候,几乎感知不到这种异常的存在,因为reloadData返回之后,下一次loop就开始执行异步的操作了。但是当列表界面的数据有可能经常变化的时候,尤其是在多线程的场景下,就会出现偶现的bug了。