Grand Central Dispatch(GCD)介绍

基础知识,block是很多高级特性的基础,包括GCD,ReactiveCocoa。

GCD操作方式有两种:
同步dispatch_sync(<#dispatch_queue_t queue#>, <#^(void)block#>)
异步dispatch_async(<#dispatch_queue_t queue#>, <#^(void)block#>).

同步则是等block代码执行完毕才回跳转到方法下一步,异步则是直接顺序执行完当前方法,线程处理block。

串行和并行队列介绍:

  1. serial queues(串行队列)又称私有调度队列(private),一般用再对特定资源的同步访问上。我们可以根据需要创建任意数量的串行队列,每一个串行队列之间是并发的。

  2. 并行队列,又称global dispatch queue。并行队列虽然可以并发的执行多个任务,但是任务开始执行的顺序和其加入队列的顺序相同。我们自己不能去创建并行调度队列。只有三个可用的global concurrent queues。

  3. main dispatch queue 是一个全局可用的串行队列,其在行用程序的主线程上执行任务。此队列的任务和应用程序的主循环(run loop)要执行的事件源交替执行。因为其运行在应用程序的主线程,main queue经常用来作为应用程序的一个同步点。

放上一段经典代码:

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    // 处理耗时操作的代码块...
    
    //通知主线程刷新
    dispatch_async(dispatch_get_main_queue(), ^{
        //回调或者说是通知主线程刷新,
    });
    
});

这是GCD最常用的一种手法,先开启一个异步操作,指定queue,block实现模块。

主队列dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();

全局dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

串行dispatch_queue_t queue = dispatch_queue_create("navy", NULL);

并行dispatch_queue_t queue = dispatch_queue_create("navy", DISPATCH_QUEUE_CONCURRENT);

queue有三类:

  • Main Dispatch Queue: 主线程队列(Serial Queue), 在程序的RunLoop中执行。因为main queue是与主线程相关的,所以这是一个串行队列。

  • Global Dispatch Queue: 系统中所有应用程序共用的全局队列(Concurrent Queue), 全局队列是并发队列,一般不必创建新的Dispatch Queue,使用此Queue就可以了。Global Diapacth Queue有4个优先级:High Priority(高)、Default Priority(默认)、Low Priority(低)、Background Priority(后台)

  • 用户队列:是用函数 dispatch_queue_create创建的队列. 这些队列可以是串行或者并行的,null默认为串行。

处理完费时的工作需要刷新UI就得dispatch_get_main_queue()在主线程里刷新界面。

dispatch_semaphore_t
信号量的作用,就是下一步代码的执行要等待上一步的计算结果。有些队列既希望能异步执行,又希望能获取到block的结果再执行下一步操作。

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    sleep(5);
    WWLog(@"block");
    dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
WWLog(@"finish");

这段代码先打印block,再打印finish。信号量要等待block执行结束。

这些基础清楚后,很容易写一个异步模块,抛弃NSThread,NSLock这类东西。

dispatch_once
字面很容易理解,只执行一次,常用于单例模式的初始化,如下:

+ (instancetype)sharedInstance
{
    static RFTagDao *instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[RFTagDao alloc] init];
    });
    return instance;
}    

dispatch_after()
延迟加载

double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    NSLog(@"Waitted at least 2 seconds");
});

dispatch group
可以等待这个组里面的queue执行完再进行下一步。

dispatch_queue_t queue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_queue_t queue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_group_dispatchGroup = dispatch_group_create();
for (id obj in arr1) {
	dispatch_group_async(dispatchGroup, queue1, ^{
    	[obj performTask];
    });
}
for (id obj in arr2) {
	dispatch_group_async(dispatchGroup, queue2, ^{
    	[obj performTask];
    });
}

//如果需要阻塞线程可以使用wait
dispatch_group_wait(dispatchGroup, DISPATCH_TIME_FOREVER);
//to do something.

//不阻塞使用notify
dispatch_queue_t notifyQueue = dispatch_get_main_queue();
dispatch_group_notify(dispatchGroup,notifyQueue,^{
	//to do something.
});

dispatch_apply
遍历执行block

group中的for循环遍历执行async block就可以修改为使用apply方式,如下:
dispatch_apply(arr1.count, queue1, ^(size_t){
		id obj = arr[i];
    	[obj performTask];
    });

不过dispatch_apply会持续阻塞,直到所有任务都执行完毕为止,如果把block指派给了current queue或者高于当前队列的某个串行队列,就会导致死锁。如果想后台执行,还是使用group安心的使用异步遍历吧。

dispatch_set_target_queue
dispatch_set_target_queue(<#dispatch_object_t object#>, <#dispatch_queue_t queue#>)
第一个参数是目标queue1,第二个是需要设置的queue2。
将queue1的的优先级设置为queue2的优先级。
这个方法不管queue1是串行还是并行队列。

dispatch_queue_set_specific
最后介绍dispatch_queue_set_specific这个东西,类似objc_setAssociatedObject objc_getAssociatedObject这套玩意,在fmdb源码中就用了这个,他所有的操作都是在一个队列里进行的。

static const void * const kDispatchQueueSpecificKey = &kDispatchQueueSpecificKey;

_queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);

dispatch_queue_set_specific(_queue, kDispatchQueueSpecificKey, (__bridge void *)self, NULL);

FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey);

assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock");

这么做的主要目的就是避免死锁,设置源码如下:

dispatch_queue_set_specific(dispatch_queue_t queue, const void *key,
void *context, dispatch_function_t destructor);
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)

specific设置key value都是用的void指针,也就意味着能够存储任意数据,甚至当前对象,好处是gcd属于底层api,和corefoundation无缝桥接。

(lldb) thread list 命令将会在控制台打印出所有队列的名字

dispatch_benchmark
用来测试性能,能测试代码运行了多少ns,不过这是私有api,不能用在发布版本。

size_t const objectCount = 1000;
uint64_t n = dispatch_benchmark(10000, ^{
@autoreleasepool {
    id obj = @42;
    NSMutableArray *array = [NSMutableArray array];
    for (size_t i = 0; i < objectCount; ++i) {
        [array addObject:obj];
    }
}
});
NSLog(@"-[NSMutableArray addObject:] : %llu ns", n);

介绍这些应该就足够使用了,还有很多文件相关的GCD知识,只是我并不常用,就不介绍了。这有一个详细的参考链接
iOS相关技术真正发生改变还是在于语言的改进上,在没有block之前,大部分都是delegate调用,或者脏的不行的KVO上。

有了block,代码能够更清晰直观,一个回调就不用写长长的delegate;因为block,然后CGD的出现,让线程队列也变的更容易直观;因为GCD,然后ReactiveCocoa能写更容易的响应式代码,整个的链式语法,核心signal,统一了消息传递。

动态语言早有的特性,iOS其实出的还是慢了点,现在swift也是汲取了动态语言的很多特性,可惜我还没怎么学习,不过毕竟用过python,js,学起来应该还是很容易。

不过最后还是吐槽下,并不是争语言的好坏,而是我觉得太久用动态语言或者各种语法糖结构,其实很容易养成不好的习惯,很容易让代码变得不可读,如果能做到代码即注释,便是极好的。

发表评论

电子邮件地址不会被公开。 必填项已用*标注