Mac OS 和 iOS采取"异步设计方式"来解决并发编程的问题。包括Grand Central Dispatch(GCD)和Operation Queue。
1、Grand Central Dispatch(GCD):
GCD是基于C的执行自定义任务机制。系统管理线程,你不需要编写线程代码。只需定义想要执行的任务,然后添加到适当的dispatch queue。GCD会负责创建线程和调度你的任务。因为GCD是线程管理系统的一部分,直接提供线程管理,比应用实现更加高效。
dispatch queue按先进先出的顺序,串行或并发地执行任务。
serial dispatch queue一次只能执行一个任务,直接当前任务完成才开始出列并启动下一个任务。
concurrent dispatch queue则尽可能多地启动任务并发执行。
2、Dispatch Sources:
Dispatch Sources是基于C的系统事件异步处理机制。一个Dispatch Source封装了一个特定类型的系统事件,当事件发生时提交一个特定的block对象或函数到dispatch queue。
3、Operation Queue:
Operation Queue 是Objective-C对象,类似于dispatch queue。你定义想要执行的任务,并添加任务到operation queue,后者负责调度和执行这些任务。和GCD一样,Operation Queue也管理了线程,更加高效。
由 NSOperationQueue类实现。dispatch queue总是按先进先出的顺序执行任务,虽然operation queue总是并发地执行任务,你可以使用依赖,在需要时确保顺序执行。
在必须实时运行的时候,线程仍然是一个很好的实现方式,。调度队列尽一切努力尽可能快地执行他们的任务,但他们并不满足实时要求。如果从后台中运行的代码需要更多可预测的行为,线程依然是更好的选择。
Operation是一种用面向对象封装的异步操作技术。Operation即可以单独使用,也可与Operation queue联合使用。因为是用Objective-C实现的,Operation主要用于基于Cocoa的iOS/Mac OS X程序中。
Operation queue是Cocoa对"并发dispatch queue"(C语言API)的封装。虽然dispatch queue总是以先进先出的方式执行任务,operation queue会考虑诸如依赖关系,优先级等因素来执行任务。
具体来说,operation对象就是NSOperation类的实例。NSOperation定义于Foundation framework之中,实现了一些基础功能,但并不完整,所以必须基于它派生新的子类,才能完成你想要的功能。
同时,Foundation framework里提供了两种常用的NSOperation子类——NSInvocationOperation及NSBlockOperation,可供直接使用于代码中。
NSInvocationOperation用于将一个selector包装成operation对象。
NSBlockOperation用于将一个或多个block对象包装成operation对象,多个block对象在operation对象中被并发的执行,只有当所有block完成之后operation才算完成。
所有的operation对象都支持以下特性,
operation对象之间可以建立图状的执行依赖关系
在整个operation完成后额外执行一个block(用于通知任务完成)
支持KVO方式来观察operation对象的状态
按优先级执行
中止正在执行的operation
operation对象的设计目的是帮助你提高程序的并发性,让你更专注于业务逻辑开始而非如何实现并发。
可以将operation对象加入operation queue来执行,也可以手动的调用operation对象的start方法来执行,但这样便不能保证operation与其它代码之间是并发的。isCurrent方法可以查看operation是以同步还是异步的方式运行的。默认情况下,返回值是NO,也就是说是以同步方式执行的。
若要实现并发的operation(即以异步方式执行),需要额外编写一些代码。比如,可能需要创建一个独立的线程,在这个线程里执行start。
对于大多数开发者而言并不需要去自己实现并发的operation,因为直接把operation对象加入operation queue即可,除非你想实现并发但又不想把对象加入到operation queue里。
若提交一个非并发的operation到operation queue里,queue自己会为你创建一个线程来执行,因此,事实上相对于其它operation这仍是异步的(除非operation之间有依赖关系)。
基于Objective-C,因此基于Cocoa的应用通常会使用Operation Queues。
1、operation object:
是 NSOperation类的实例,封装了应用需要执行的任务,和执行任务所需的数据。
NSOperation 本身是抽象基类,我们必须实现子类。Foundation framework提供了两个具体子类,你可以直接使用:
NSInvocationOperation:基于应用的一个对象和selector来创建operation object。
NSBlockOperation:用来并发地执行一个或多个block对象。operation object使用"组"的语义来执行多个block对象,所有相关的block都执行完成之后,operation object才算完成。?
2、并发
如果你需要实现并发operation,也就是相对调用线程异步执行的操作。你必须添加额外的代码,来异步地启动操作。例如生成一个线程、调用异步系统函数,以确保start方法启动任务,并立即返回。
3、创建一个 NSInvocationOperation对象
@implementation MyCustomClass
- (NSOperation*)taskWithData:(id)data {
NSInvocationOperation* theOp = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(myTaskMethod:) object:data];
return theOp;
}
// This is the method that does the actual work of the task.
- (void)myTaskMethod:(id)data {
// Perform the task.
}
@end
4、创建一个 NSBlockOperation对象
NSBlockOperation* theOp = [NSBlockOperation blockOperationWithBlock: ^{
NSLog(@"Beginning operation.\n");
// Do some work.
}];
5、自定义Operation对象
1)每个operation对象至少需要实现以下方法:
a)自定义initialization方法:初始化,将operation对象设置为已知状态
b)自定义main方法:执行你的任务
@interface MyNonConcurrentOperation : NSOperation
@property id (strong) myData;
-(id)initWithData:(id)data;
@end
@implementation MyNonConcurrentOperation
- (id)initWithData:(id)data {
if (self = [super init])
myData = data;
return self;
}
-(void)main {
@try {
// Do some work on myData and report the results.
}
@catch(...) {
// Do not rethrow exceptions.
}
}
@end
2)operation对象定期地调用 isCancelled方法,如果返回YES(表示已取消),则立即退出执行。
- (void)main {
@try {
BOOL isDone = NO;
while (![self isCancelled] && !isDone) {
// Do some work and set isDone to YES when finished
}
}
@catch(...) {
// Do not rethrow exceptions.
}
}
3)为并发执行配置operations
Operation对象默认按同步方式执行。但是如果你希望手工执行operations,而且仍然希望能够异步执行操作,就必须定义operation对象使其成为一个并发操作。
@interface MyOperation : NSOperation {
BOOL executing;
BOOL finished;
}
- (void)completeOperation;
@end
@implementation MyOperation
- (id)init {
self = [super init];
if (self) {
executing = NO;
finished = NO;
}
return self;
}
- (BOOL)isConcurrent {
return YES;
}
- (BOOL)isExecuting {
return executing;
}
- (BOOL)isFinished {
return finished;
}
@end
- (void)start {
// Always check for cancellation before launching the task.
if ([self isCancelled])
{
// Must move the operation to the finished state if it is canceled.
[self willChangeValueForKey:@"isFinished"];
finished = YES;
[self didChangeValueForKey:@"isFinished"];
return;
}
// If the operation is not canceled, begin executing the task.
[self willChangeValueForKey:@"isExecuting"];
[NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
executing = YES;
[self didChangeValueForKey:@"isExecuting"];
}
- (void)main {
@try {
// Do the main work of the operation here.
[self completeOperation];
}
@catch(...) {
// Do not rethrow exceptions.
}
}
- (void)completeOperation {
[self willChangeValueForKey:@"isFinished"];
[self willChangeValueForKey:@"isExecuting"];
executing = NO;
finished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
}
4)维护KVO依从
NSOperation类的key-value observing(KVO)依从于以下key paths:
isCancelled,
isConcurrent,
isExecuting,
isFinished,
isReady,
dependencies,
queuePriority,
completionBlock。
6、自定义一个Operation对象的执行行为
对Operation对象的配置发生在创建对象之后,将其添加到queue之前。
包括配置operation之间的依赖关系、修改Operation的执行优先级、修改底层线程的优先级、设置一个completion block(在Mac OS X 10.6之后,operation可以在主任务完成之后执行一个completion block。可以使用这个completion block来执行任何不属于主任务的工作。怎么没提到iOS????)
8、为Operation对象确定一个适当的范围
和任何对象一样,NSOperation对象也会消耗内存,执行时也会带来开销。因此如果operation对象只做很少的工作,但是却创建成千上万个小的operation对象,你就会发现更多的时间花在了调度operations而不是执行它们。
要高效地使用Operations,关键是在Operation执行的工作量和保持计算机繁忙之间,找到最佳的平衡。确保每个Operation都有一定的工作量可以执行。例如100个operations执行100次相同任务,可以考虑换成10个operations,每个执行10次。
你同样要避免向一个queue中添加过多的operations,或者持续快速地向queue中添加operation,超过queue所能处理的能力。这里可以考虑分批创建operations对象,在一批对象执行完之后,使用completion block告诉应用创建下一批operations对象。
9、执行Operations
1)添加Operations到Operation Queue
NSOperationQueue* aQueue = [[NSOperationQueue alloc] init];
[aQueue addOperation:anOp]; // Add a single operation
[aQueue addOperations:anArrayOfOps waitUntilFinished:NO]; // Add multiple operations
[aQueue addOperationWithBlock:^{
/* Do something. */
}];
2)手动执行Operations
手动执行Operation,要求Operation已经准备好,isReady返回YES,此时你才能调用start方法来执行它。isReady方法与Operations依赖是结合在一起的。
- (BOOL)performOperation:(NSOperation*)anOp
{
BOOL ranIt = NO;
if ([anOp isReady] && ![anOp isCancelled])
{
if (![anOp isConcurrent])
[anOp start];
else
[NSThread detachNewThreadSelector:@selector(start)
toTarget:anOp withObject:nil];
ranIt = YES;
}
else if ([anOp isCancelled])
{
// If it was canceled before it was started,
// move the operation to the finished state.
[self willChangeValueForKey:@"isFinished"];
[self willChangeValueForKey:@"isExecuting"];
executing = NO;
finished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
// Set ranIt to YES to prevent the operation from
// being passed to this method again in the future.
ranIt = YES;
}
return ranIt;
}
3)取消Operations
你可以调用Operation对象的cancel方法取消单个操作,也可以调用operation queue的cancelAllOperations 方法取消当前queue中的所有操作
4)等待Operations完成
使用 NSOperation的 waitUntilFinished方法等待operation完成。通常我们应该避免编写这样的代码,阻塞当前线程可能是一种简便的解决方案,但是它引入了更多的串行代码,限制了整个应用的并发性,同时也降低了用户体验。
绝对不要在应用主线程中等待一个Operation,只能在第二或次要线程中等待。阻止主线程将导致应用无法响应用户事件,应用也将表现为无响应。
5)挂起和继续Queue
如果你想临时挂起Operations的执行,可以使用 setSuspended:方法暂停相应的queue。不过挂起一个queue不会导致正在执行的Operation在任务中途暂停,只是简单地阻止调度新Operation执行。你可以在响应用户请求时,挂起一个queue,来暂停等待中的任务。稍后根据用户的请求,可以再次调用 setSuspended: 方法继续Queue中操作的执行。
1、简介
1)GCD提供了几种dispatch queues,
a)Serial(串行):也称为private dispatch queue,每次只执行一个任务,按任务添加顺序执。可以创建任意数量的串行queues,虽然每个queue本身每次只能执行一个任务,但是各个queue之间是并发执行的
b)Concurrent(并发):也称为global dispatch queue,可以并发执行一个或多个任务,但是任务仍然是以添加到queue的顺序启动。每个任务运行于独立的线程中,dispatch queue管理所有线程。同时运行的任务数量随时都会变化,而且依赖于系统条件。你不能创建并发dispatch queues。相反应用只能使用三个已经定义好的全局并发queues。?
c)Main dispatch queue:全局可用的串行queue,在应用主线程中执行任务。这个queue与应用的?run loop 交叉执行由于它运行在应用的主线程,main queue通常用于应用的关键同步点。虽然你不需要创建main dispatch queue,但你必须确保应用适当地回收。
2)dispatch queues的几个关键点:
a)dispatch queues相对其它dispatch queues并发地执行任务,串行化任务只能在同一个dispatch queue中实现。
b)系统决定了同时能够执行的任务数量,应用在100个不同的queues中启动100个任务,并不表示100个任务全部都在并发地执行(除非系统拥有100或更多个核)
c)系统在选择执行哪个任务时,会考虑queue的优先级。
d)queue中的任务必须在任何时候都准备好运行,注意这点和Operation对象不同。
e)private dispatch queue是引用计数的对象。你的代码中需要retain这些queue,另外dispatch source也可能添加到一个queue,从而增加retain的计数。因此你必须确保所有dispatch source都被取消,而且适当地调用release。
2、Queue相关的技术
1)Dispatch group:
用于监控一组block对象完成(你可以同步或异步地监控block)。Group提供了一个非常有用的同步机制,你的代码可以等待其它任务的完成
2)Dispatch semaphore:
类似于传统的semaphore(信号量),但是更加高效。只有当调用线程由于信号量不可用,需要阻塞时,Dispatch semaphore才会去调用内核。如果信号量可用,就不会与内核进行交互。使用信号量可以实现对有限资源的访问控制
3)Dispatch source
Dispatch source在特定类型的系统事件发生时,会产生通知。你可以使用dispatch source来监控各种事件,如:进程通知、信号、描述符事件、等等。当事件发生时,dispatch source异步地提交你的任务到指定的dispatch queue,来进行处理。
3、使用Block实现任务
设计Block时需考虑以下关键指导方针:
1)对于使用dispatch queue的异步Block,可以在Block中安全地捕获和使用父函数或方法中的scalar变量。但是Block不应该去捕获大型结构体或其它基于指针的变量,这些变量由Block的调用上下文分配和删除。在你的Block被执行时,这些指针引用的内存可能已经不存在。当然,你自己显式地分配内存(或对象),然后让Block拥有这些内存的所有权,是安全可行的。
2)Dispatch queue对添加的Block会进行复制,在完成执行后自动释放。换句话说,你不需要在添加Block到Queue时显式地复制
3)尽管Queue执行小任务比原始线程更加高效,仍然存在创建Block和在Queue中执行的开销。如果Block做的事情太少,可能直接执行比dispatch到queue更加有效。使用性能工具来确认Block的工作是否太少
4)绝对不要针对底层线程缓存数据,然后期望在不同Block中能够访问这些数据。如果相同queue中的任务需要共享数据,应该使用dispatch queue的context指针来存储这些数据。
5)如果Block创建了大量Objective-C对象,考虑创建自己的autorelease pool,来处理这些对象的内存管理。虽然GCD dispatch queue也有自己的autorelease pool,但不保证在什么时候会回收这些pool。
4、创建和管理Dispatch Queue
1)获得全局并发Dispatch Queue
系统给每个应用提供三个并发dispatch queue,所有应用全局共享,三个queue的区别是优先级。你不需要显式地创建这些queue,使用 dispatch_get_global_queue 函数来获取这三个queue:
dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
除了默认优先级的并发queue,你还可以获得高和低优先级的两个,分别使用DISPATCH_QUEUE_PRIORITY_HIGH和 DISPATCH_QUEUE_PRIORITY_LOW常量来调用上面函数。
2)创建串行Dispatch Queue
dispatch_queue_t queue;
queue = dispatch_queue_create("com.example.MyQueue", NULL);
3)运行时获得公共Queue
GCD提供函数,让应用访问几个公共dispatch queue:
a)dispatch_get_current_queue:作为调试用途,或者测试当前queue的标识。在block对象中调用这个函数会返回block提交到的queue(这个时候queue应该正在执行中)。在block对象之外调用这个函数会返回应用的默认并发queue。
b)dispatch_get_main_queue:函数获得应用主线程关联的串行dispatch queue。Cocoa应用、调用了 dispatch_main 函数或配置了run loop(CFRunLoopRef类型或一个 NSRunLoop对象)的应用,会自动创建这个queue。
c)dispatch_get_global_queue:来获得共享的并发queue
4)Dispatch Queue的内存管理
Dispatch Queue和其它dispatch对象都是引用计数的数据类型。
5)在Queue中存储自定义上下文信息
所有dispatch对象(包括dispatch queue)都允许你关联custom context data。使用dispatch_set_context 和 dispatch_get_context函数来设置和获取对象的上下文数据。系统不会使用你的上下文数据,所以需要你自己在适当的时候分配和销毁这些数据。
6)为Queue提供一个清理函数
在创建串行dispatch queue之后,可以附加一个finalizer函数,在queue被销毁之前执行自定义的清理操作。使用 dispatch_set_finalizer_f 函数为queue指定一个清理函数,当queue的引用计数到达0时,就会执行该清理函数。你可以使用清理函数来解除queue关联的上下文数据,而且只有上下文指针不为NULL时才会调用这个清理函数。
下面例子演示了自定义finalizer函数的使用,你需要自己提供myInitializeDataContextFunction和 myCleanUpDataContextFunction函数,用于初始化和清理上下文数据。
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;
}
5、添加任务到Queue
1)添加单个任务到Queue
你可以异步或同步地添加一个任务到Queue,尽可能地使用 dispatch_async或dispatch_async_f函数异步地dispatch任务。因为添加任务到Queue中时,无法确定这些代码什么时候能够执行。因此异步地添加block或函数,可以让你立即调度这些代码的执行,然后调用线程可以继续去做其它事情。
重要:绝对不要在任务中调用 dispatch_sync或 dispatch_sync_f函数,并同步dispatch新任务到当前正在执行的queue。对于串行queue这一点特别重要,因为这样做肯定会导致死锁;而并发queue也应该避免这样做。
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");
Dispatch Queues
Adding Tasks to a Queue
2)任务完成时执行Completion Block
Completion Block是你dispatch到queue的另一段代码,在原始任务完成时自动执行。
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);
});
}
3)并发地执行Loop Iteration
注意:和普通for循环一样,dispatch_apply和 dispatch_apply_f函数也是在所有迭代完成之后才会返回。因此在queue上下文执行的代码中再次调用这两个函数时,必须非常小心。如果你传递的参数是串行queue,而且正是执行当前代码的Queue,就会产生死锁。
另外这两个函数还会阻塞当前线程,因此在主线程中调用这两个函数同样必须小心,可能会阻止事件处理循环并无法响应用户事件。所以如果循环代码需要一定的时间执行,你可以考虑在另一个线程中调用这两个函数。
下面代码使用 dispatch_apply替换了for循环,你传递的block必须包含一个参数,用来标识当前循环迭代。第一次迭代这个参数值为0,第二次时为1,最后一次值为count - 1。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
0);
dispatch_apply(count, queue, ^(size_t i) {
printf("%u\n",i);
});
4)在主线程中执行任务
GCD提供一个特殊dispatch queue,可以在应用的主线程中执行任务。应用主线程设置了run loop(由CFRunLoopRef类型或 NSRunLoop 对象管理),就会自动创建这个queue,并且自动drain。非Cocoa应用如果不显式地设置run loop,就必须显式地调用dispatch_main 函数来显式地drain这个dispatch queue。否则虽然你可以添加任务到queue,但任务永远不会被执行。
调用 dispatch_get_main_queue函数获得应用主线程的dispatch queue。添加到这个queue的任务由主线程串行化执行,因此你可以在应用的某些地方使用这个queue作为同步点。
5)任务中使用Objective-C对象
GCD支持Cocoa内存管理机制,因此可以在提交到queue的block中自由地使用Objective-C对象。每个dispatch queue维护自己的autorelease pool确保释放autorelease对象,但是queue不保证这些对象实际释放的时间
6、挂起和继续queue
我们可以暂停一个queue以阻止它执行block对象,使用 dispatch_suspend函数挂起一个dispatch queue;使用 dispatch_resume函数继续dispatch queue。调用 dispatch_suspend会增加queue的引用计数,调用 dispatch_resume则减少queue的引用计数。当引用计数大于0时,queue就保持挂起状态。因此你必须对应地调用suspend和resume函数。
7、使用Dispatch Semaphore控制有限资源的使用
使用dispatch semaphore的过程如下:
a)使用 dispatch_semaphore_create函数创建semaphore,指定正数值表示资源的可用数量。
b)在每个任务中,调用 dispatch_semaphore_wait来等待Semaphore
c)当上面调用返回时,获得资源并开始工作
d)使用完资源后,调用 dispatch_semaphore_signal函数释放和signal这个semaphore
// Create the semaphore, specifying the initial pool size
dispatch_semaphore_t fd_sema = dispatch_semaphore_create(getdtablesize() / 2);
// Wait for a free file descriptor
dispatch_semaphore_wait(fd_sema, DISPATCH_TIME_FOREVER);
fd = open("/etc/services", O_RDONLY);
// Release the file descriptor when done
close(fd);
dispatch_semaphore_signal(fd_sema);
8、等待queue中的一组任务
Dispatch group用来阻塞一个线程,直到一个或多个任务完成执行。有时候你必须等待任务完成的结果,然后才能继续后面的处理。dispatch group也可以替代线程join。
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);
9、Dispatch Queue和线程安全性
使用Dispatch Queue实现应用并发时,也需要注意线程安全性:
a)Dispatch queue本身是线程安全的。换句话说,你可以在应用的任意线程中提交任务到dispatch queue,不需要使用锁或其它同步机制。
b)不要在执行任务代码中调用 dispatch_sync函数调度相同的queue,这样做会死锁这个queue。如果你需要dispatch到当前queue,需要使用 dispatch_async 函数异步调度
c)避免在提交到dispatch queue的任务中获得锁,虽然在任务中使用锁是安全的,但在请求锁时,如果锁不可用,可能会完全阻塞串行queue。类似的,并发queue等待锁也可能阻止其它任务的执行。如果代码需要同步,就使用串行dispatch queue。
d)虽然可以获得运行任务的底层线程的信息,最好不要这样做。
1、关于Dispatch Source
dispatch source是基础数据类型,协调特定底层系统事件的处理。GCD支持以下dispatch source:
Timer dispatch source:定期产生通知
Signal dispatch source:UNIX信号到达时产生通知
Descriptor dispatch source:各种文件和socket操作的通知
数据可读
数据可写
文件在文件系统中被删除、移动、重命名
文件元数据信息改变
Process dispatch source:进程相关的事件通知
当进程退出时
当进程发起fork或exec等调用
信号被递送到进程
Mach port dispatch source:Mach相关事件的通知
Custom dispatch source:你自己定义并自己触发
Dispatch source替代了异步回调函数,来处理系统相关的事件。
2、创建Dispatch Source
创建相应的dispatch source:
a)使用 dispatch_source_create函数创建dispatch source
b)配置dispatch source:
为dispatch source设置一个事件处理器
对于定时器源,使用 dispatch_source_set_timer函数设置定时器信息
c)为dispatch source赋予一个取消处理器(可选)
d)调用 dispatch_resume函数开始处理事件
由于dispatch source必须进行额外的配置才能被使用,dispatch_source_create函数返回的dispatch source将处于挂起状态。此时dispatch source会接收事件,但是不会进行处理。这时候你可以安装事件处理器,并执行额外的配置。
1)编写和安装一个事件处理器
dispatch_source_set_event_handler或 dispatch_source_set_event_handler_f安装事件处理器。
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
myDescriptor, 0, myQueue);
dispatch_source_set_event_handler(source, ^{
// Get some data from the source variable, which is captured
// from the parent context.
size_t estimated = dispatch_source_get_data(source);
// Continue reading the descriptor...
});
dispatch_resume(source);
下面是事件处理器能够获得的事件信息:
dispatch_source_get_handle
dispatch_source_get_data
dispatch_source_get_mask
2)安装一个取消处理器
取消处理器在dispatch soruce释放之前执行清理工作。多数类型的dispatch source不需要取消处理器,除非你对dispatch source有自定义行为需要在释放时执行。但是使用描述符或Mach port的dispatch source必须设置取消处理器,用来关闭描述符或释放Mach port。
。使用 dispatch_source_set_cancel_handler或 dispatch_source_set_cancel_handler_f函数来设置取消处理器
3)修改目标Queue
使用 dispatch_set_target_queue函数在任何时候修改目标queue。
4)关联自定义数据到dispatch source
使用 dispatch_set_context函数关联自定义数据到dispatch source。
5)Dispatch Source的内存管理
Dispatch Source也是引用计数的数据类型,初始计数为1,可以使用 dispatch_retain和dispatch_release 函数来增加和减少引用计数。引用计数到达0时,系统自动释放dispatch source数据结构。
3、示例
4、取消一个Dispatch Source
void RemoveDispatchSource(dispatch_source_t mySource)
{
dispatch_source_cancel(mySource);
dispatch_release(mySource);
}
5、挂起和继续Dispatch Source
你可以使用 dispatch_suspend和 dispatch_resume临时地挂起和继续dispatch source的事件递送。这两个函数分别增加和减少dispatch对象的挂起计数。因此,你必须每次dispatch_suspend调用之后,都需要相应的 dispatch_resume 才能继续事件递送。
挂起一个dispatch source期间,发生的任何事件都会被累积,直到dispatch source继续。但是不会递送所有事件,而是先合并到单一事件,然后再一次递送。例如你监控一个文件的文件名变化,就只会递送最后一次的变化事件。