GCD 入门与进阶

后端存储 DOPCN

GCD 对我来说是初入门 iOS 开发的记忆,那个时候还不知道 UIKit 中的类应该在主线程调用,所以异步获取了数据之后虽然调用了 reloadData,但是界面怎么都不刷新,心慌慌查了好久才解决这个问题,现在回想起来也是蛮有趣。这可以说明 GCD 的引入,真的大大降低了进行多线程操作的门槛,然而要解决多线程带来的一些难题却依旧不那么容易。

多线程综述

从我开始做 iOS 开发以来从没有在项目中直接使用过 NSThread 对象,所以一直没有什么直观的印象,直到简单了解了 pthread 之后才清晰的认识到 NSThread 其实是对 pthread 的简单封装面向对象化,在 iOS 10 中 NSThread 新增了使用 block 直接初始化的方法,这才使得 NSThread 的使用简化了不少,而在这之前和 pthread 一样不够优雅。在 pthread 中使用 pthread_create 创建线程,要传入函数指针作为回调,早先的 NSThread 则是标准的 cocoa 风格 target selector。

NSThread 对 pthread 的封装只是胶水式的薄薄封装,并没有提供其他并发相关功能。真正实现更高级封装的是 NSOperationQueue 和 GCD,NSOperationQueue 在引入 GCD 之后应该是使用 GCD 重写了,所以 iOS8 新增了 underlyingQueue 这个属性,返回一个 dispatch_queue_t。我曾经在面试里被鄙视过:GCD?GCD 能实现 NSOperation 的依赖关系吗?那个时候我没有回答上来,如果再给我一次回答的机会,我会说:可以,只是麻烦一些。这就要说到一些 GCD 的进阶功能。

GCD 进阶

面向对象是一种提高抽象能力很好的手段,已数据的形式封装掉了很多细节的逻辑。要实现 NSOperation 的依赖关系,可以使用 dispatch_group。SDK 的头文件是这样介绍的:A group of blocks submitted to queues for asynchronous invocation。将一组 block 作为一个“对象”来管理(dispatch_group_t 当然并不是一个对象,而是一个和 ObjC 对象很像的结构体,可以通过配置由 ARC 管理生命周期),常用的方法有两对 dispatch_group_wait dispatch_group_notify 和 dispatch_group_enter dispatch_group_leave。在增加依赖的时候进行 group_enter,在依赖的 block 执行完后 group_leave,这就是 swift forelimbs foundation 中的实现方式。dispatch_group 所实现的组管理,类似于 pthread_join。

iOS8 之后 block 不再是简单的 block 了,可以是 dispatch_block_t。虽然看定义 typedef void (^dispatch_block_t)(void); 它只是一个空参数空返回 的 block,但是通过API dispatch_block_create 为 block 指定 flags,通过这个接口创建出来的 dispatch_block_t 还可以 dispatch_block_wait,dispatch_block_notify,dispatch_block_cancel,这样就解决了另一个问题:block 能像 NSOperation 一样取消吗?答案是可以。

另一个进阶 dispatch 库成员就是 dispatch_semaphore 了。在 AFNetworking 里有一处应用,使用 dispatch_semaphore 从异步的 block 里取出值同步的返回:

- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
    __block NSArray *tasks = nil;
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
            tasks = dataTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
            tasks = uploadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
            tasks = downloadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
            tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
        }

        dispatch_semaphore_signal(semaphore);
    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    return tasks;
}

NSURLSession 的接口全部都是异步执行回调,所以要返回异步block 的参数,就必须等异步block 执行完,知道这个大概可以吹半年。

其他应用

除了这些还有一些其他少见应用:

dispatch_apply 实现多线程迭代,和使用 enumerateObjectsWithOptions 时指定 NSEnumerationConcurrent 差不多,迭代过程中最好不要有共用资源。

dispatch_source_timer 和 NSTimer 不同不依赖 runloop,所以不会受 runloop mode 影响。

其他相关

虽然说 GCD 是真正的高级封装,但是有些地方还是可以很明显的暴露出 pthread 的特点,特别是一些命名例如:pthread_getspecific,dispatch_queue_get_specific;pthread_setspecific,dispatch_queue_set_specific。

用 GCD 代替加锁实现线程安全访问,例子是 FMDatabaseQueue 的实现。其实就是 GCD 的方法中进行了加锁,不然 dispatch_sync 到当前线程为什么会死锁。但好处是抽象层级更高,不用手动处理锁相关。

稿源:DOPCN (源链) | 关于 | 阅读提示

本站遵循[CC BY-NC-SA 4.0]。如您有版权、意见投诉等问题,请通过eMail联系我们处理。
酷辣虫 » 后端存储 » GCD 入门与进阶

喜欢 (0)or分享给?

专业 x 专注 x 聚合 x 分享 CC BY-NC-SA 4.0

使用声明 | 英豪名录