当代码中需要异步执行一些工作的时候,需要用到异步编程的技术。在iOS下进行并行编程的方法有四种:
1. Operation Queues:把要执行的工作打包成一个Objective-C对象,并且该对象的class类型必须继承自NSOperation。
2. Dispatch Queues:把要执行的工作打包成一个函数或一个block对象。有两种该类型的queue:serial dispatch queue,concurrent dispatch queue。区别在于:serial dispatch queue采取FIFO的原则,执行完一个task之后,再去从queue里取出另外一个task执行,也就是说第二个task开始执行时,第一个task肯定都执行完了。而concurrent dispatch queue虽然也是采取FIFO的原则,但后面的task不会等前面的task执行完后再执行,而是真的并行。
3. Dispatch Sources:异步的处理来自系统的事件。
4. 线程:用线程相关的函数创建一个新的线程,在新线程里完成需要异步进行的工作。这是最原始的方法,在iOS上不鼓励。
在平常的工作中,最常用的是 Dispatch Queues ,其他三种几乎不用。下面就重点介绍下 Dispatch Queues 。
Dispatch Queues的类型
| 描述 |
---|---|
Serial | 又被称为private dispatch queues,会在一个不同的线程按照FIFO的顺序执行task。可通过dispatch_queue_create创建任意数量的 serial queue,注意的是各个queue之间是完全并行的,但每一个queue内的task是按照顺序一个一个执行的。 |
Concurrent | 又被称为global dispatch queue,task依然会按照FIFO的顺序取出执行,但下一个task不会等待上一个task完成才去执行,task之间 是完全并行的。这些task实际上会被扔到多个不同的线程里并行执行。系统预置了4种global dispatch queue,这4种queue的区别在 于其优先级的不同。另外,因为这些预置的queue是global的,所以不用考虑retain、release的问题。 |
Main dispatch queue | 是一个global的serial queue,只不过与main thread线程绑定了,可以在application的main thread里按照FIFO的顺序执行task。可 以通过dispatch_get_main_queue获取该queue。
|
相关API
这个接口一般用于debug或测试:在block内部调用该接口,可以得到该block被投递到哪个queue里了;在block外部调用该接口,可得到当前默认的concurrent queue。
2. dispatch_get_main_queue
得到当前与main thread绑定的 global Serial dispatch queue。
得到当前系统预置的 global Concurrent dispatch queue。
创建一个新的 Serial dispatch queue。
dispatch queue的内存管理
1. 对于自己创建的(private dispatch queue),需要通过 dispatch_retain 和 dispatch_release 管理queue对象的生存期。
2. 对于 global dispatch queue,系统会自动处理,不用程序员管了。
为queue绑定自定义数据(context data)
可以通过 dispatch_set_context
and dispatch_get_context
为所有类型的queue绑定自定义的数据,称之为context,该context数据的 allocate 和 deallocate 由程序员负责。
为Serial dispatch queue设置一个clean up的函数
创建一个Serial dispatch queue之后,可以为该queue设置一个clean up函数,该函数会在queue被 deallocate 之前调用,主要用于释放context data。如果没有为该queue绑定context data,也即context指针为NULL,那么该clean up函数将不会被调用。
示例: Installing a queue clean up function
void myFinalizerFunction(void *context)
{
MyDataContext* theData = (MyDataContext*)context;
// Clean up the contents of the structure
myCleanUpDataContextFunction(theData);
// Now release the structure itself.
free(theData);
}
dispatch_queue_t createMyQueue()
{
MyDataContext* data = (MyDataContext*) malloc(sizeof(MyDataContext));
myInitializeDataContextFunction(data);
// Create the queue and set the context data.
dispatch_queue_t serialQueue = dispatch_queue_create("com.example.CriticalTaskQueue", NULL);
if (serialQueue)
{
dispatch_set_context(serialQueue, data);
dispatch_set_finalizer_f(serialQueue, &myFinalizerFunction);
}
return serialQueue;
}
向Queue中添加task
有两种方式:同步(synchronously)或 异步(asynchronously)。
同步API: dispatch_sync
和 dispatch_sync_f
异步API:dispatch_async
和 dispatch_async_f
在实际中,使用同步的方式很少,比如为了避免race condition等。
示例:dispatching tasks asynchronously and synchronously:
dispatch_queue_t myCustomQueue;
myCustomQueue = dispatch_queue_create("com.example.MyCustomQueue", NULL);
dispatch_async(myCustomQueue, ^{
printf("Do some work here.\n");
});
printf("The first block may or may not have run.\n");
dispatch_sync(myCustomQueue, ^{
printf("Do some more work here.\n");
});
printf("Both blocks have completed.\n");
Performing a Completion Block When a Task Is Done
有时候我们希望知道被异步执行的task在什么时候完成,并且能将执行结果返回给我们,这时候就可以在task的末尾再一次通过 dispatch_async 将另一个block dispatch到指定的queue,并将task执行的结果返回给该queue。
示例:Executing a completion callback after a task
void average_async(int *data, size_t len,
dispatch_queue_t queue, void (^block)(int))
{
// Retain the queue provided by the user to make
// sure it does not disappear before the completion
// block can be called.
dispatch_retain(queue);
// Do the work on the default concurrent queue and then
// call the user-provided block with the results.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
int avg = average(data, len);
dispatch_async(queue, ^{ block(avg);});
// Release the user-provided queue when done
dispatch_release(queue);
});
}
Suspending and Resuming Queues
dispatch_suspend:让queue暂停执行blocks,并且将 suspension reference count 加 一
dispatch_resume:恢复执行blocks,并且将 suspension reference count 减 一
只要 suspension reference count 的值大于0,那该queue就是 suspended 状态,因此要平衡好 dispatch_suspend 和 dispatch_resume 的调用次数。
Waiting on Groups of Queued Tasks
如果要阻塞一个线程,以便等待一个或多个task完成,就可以采用 Dispatch groups 技术。相关API为:dispatch_group_async 和 dispatch_group_wait。
示例:Waiting on asynchronous tasks
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
// Add a task to the group
dispatch_group_async(group, queue, ^{
// Some asynchronous work
});
// Do some other work while the tasks execute.
// When you cannot make any more forward progress,
// wait on the group to block the current thread.
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// Release the group when it is no longer needed.
dispatch_release(group);