objc系列译文(2.2):iOS开发中一些常见的并行处理

385 查看

本文主要探讨一些常用多任务的最佳实践。包括Core Data的多线程访问,UI的并行绘制,异步网络请求以及一些在运行态内存吃紧的情况下处理大文件的方案等。

其实编写异步处理的程序有很多坑!所以,本文所涉及的样例都尽量采用简洁直观的处理方式。因为越是简单的逻辑结构,越能彰显代码的脉络清晰,越易于理解。打个比方,如果在程序中使用多层次的嵌套回调,基本上这个它会有很大的重构空间。

 

Operation Queues vs. Grand Central Dispatch

目前,在iOS和OS X 中,SDK主要提供了两类多任务处理的API:operation queuesGrand Central Dispatch(GCD)。其中GCD是基于C的更加底层的API,而operation queues被普遍认为是基于GCD而封装的面向对象(objective-c)的多任务处理API。关于并发处理API层面的比较,有很多相关的文章,如果感兴趣可以自行阅读。

相比于GCD,operation queues的优点是:提供了一些非常好用的便捷处理。其中最重要的一个就是可以取消在任务处理队列中的任务(稍后举例)。另外operation queues在处理任务之间的依赖关系方面也更加容易。而GCD的特长是:可以访问和操作那些operation queues所不能使用的低层函数。详情参考低层并发处理API相关文章

延伸阅读:

 

Core Data in the Background

在着手Core Data的多线程处理之前,我们建议先通读一下苹果的官方文档”Concurrency with Core Data guide”。这个文档中罗列了诸多规则,比如:不要在不同线程间直接传递managed objects。注意这意味着线程间不但不能对不属于自己的managed object做修改操作,甚至连读其中的属性都不可以。正确做法是通过传object ID和从其他线程的context信息中获取object的方式来达到传递object的效果。其实只要遵循文档中的各种指导规则,那么处理 Core Data的并行编程问题就容易多了。

Xcode提供了一种创建Core Data的模版,工作原理是通过主线程作为persistent store coordinator(持久化协调者)来操作managed object context,进而实现对象的持久化。虽然这种方式很便捷并基本适用常规场景,但如果要操作的数据比较庞大,那就非常有必要将Core Data的操作分配到其他线程中去(注:大数据量的操作可能会阻塞主线程,长时间阻塞主线程用户体验很差并且有可能导致应用程序假死或崩溃)。

样例:向Core Data中导入大量的数据:

1.为引入数据创建一个单独的operation
2.创建一个和main object context相同persistent store coordinator的object context
3.引入操作的context保存完成后,通知main managed object context去合并数据。

在样例app中,要导入一大组柏林的运输线路数据。在导入的过程中会展示进度条并且用户可以随时取消当前导入操作。等待条下面再用一个table view来展示目前已导入的数据同时边导入边刷新界面。样例采用的数据署名Creative Commons license,可以在此下载。使用公开标准的General Transit Feed格式。

接下来创建NSOperation的子类ImportOperation,通过复写main方法来处理所有的导入工作。再创建一个private queue concurrency类型的独立的managed object context,这个context需要管理自己的queue,在其上的所有操作必须使用performBlock或者performBlockAndWait来触发。这点相当重要,这是保证这些操作能在正确的线程上执行的关键。

注:在样例中复用了persistent store coordinator。正常情况下,需要初始化managed object contexts并且指定其类型:如NSPrivateQueueConcurrencyType,NSMainQueueConcurrencyType或者NSConfinementConcurrencyType,其中NSConfinementConcurrencyType不建议使用,因为它是给一些遗留的旧代码使用的。

导入前,按行迭代运输线路数据文件的内容,给每一个能解析的行数据创建一个managed object:

通过view controller中来触发操作:

至此为止,多线程导入数据到Core Data部分已经完成。接下来,是取消导入部分,非常简单只需要在集合的快速枚举block中加个判断即可:

最后是增加进度条,在operation中创建一个progressCallback属性block。注意更新进度条必须在主线程中完成,否则会导致UIKit崩溃。

在快速枚举中加上下面这行去调用进度条更新block:

然而,如果你执行样例app就会发现一切都特别慢而且取消操作也有迟滞。这是因为main opertation queue中塞满了要更新进度条的block。通过降低更新进度条的频度可以解决这个问题,
例如以百分之一的节奏更新进度条: