iOS学习笔记09—iOS并发编程

来源:互联网 发布:mac百度云破解 编辑:程序博客网 时间:2024/04/28 04:44

iOS学习笔记09—iOS并发编程

一、概述

Mac OS iOS采取"异步设计方式"来解决并发编程的问题。包括Grand Central DispatchGCD)和Operation Queue

1Grand Central DispatchGCD):

GCD是基于C的执行自定义任务机制。系统管理线程,你不需要编写线程代码。只需定义想要执行的任务,然后添加到适当的dispatch queueGCD会负责创建线程和调度你的任务。因为GCD是线程管理系统的一部分,直接提供线程管理,比应用实现更加高效。

dispatch queue按先进先出的顺序,串行或并发地执行任务。

serial dispatch queue一次只能执行一个任务,直接当前任务完成才开始出列并启动下一个任务。

concurrent dispatch queue则尽可能多地启动任务并发执行。

 

2Dispatch Sources

Dispatch Sources是基于C的系统事件异步处理机制。一个Dispatch Source封装了一个特定类型的系统事件,当事件发生时提交一个特定的block对象或函数到dispatch queue

 

3Operation Queue

Operation QueueObjective-C对象,类似于dispatch queue。你定义想要执行的任务,并添加任务到operation queue,后者负责调度和执行这些任务。和GCD一样,Operation Queue也管理了线程,更加高效。

NSOperationQueue类实现。dispatch queue总是按先进先出的顺序执行任务,虽然operation queue总是并发地执行任务,你可以使用依赖,在需要时确保顺序执行。

在必须实时运行的时候,线程仍然是一个很好的实现方式,。调度队列尽一切努力尽可能快地执行他们的任务,但他们并不满足实时要求。如果从后台中运行的代码需要更多可预测的行为,线程依然是更好的选择。 

Operation是一种用面向对象封装的异步操作技术。Operation即可以单独使用,也可与Operation queue联合使用。因为是用Objective-C实现的,Operation主要用于基于CocoaiOS/Mac OS X程序中。

Operation queueCocoa"并发dispatch queue"C语言API)的封装。虽然dispatch queue总是以先进先出的方式执行任务,operation queue会考虑诸如依赖关系,优先级等因素来执行任务。

具体来说,operation对象就是NSOperation类的实例。NSOperation定义于Foundation framework之中,实现了一些基础功能,但并不完整,所以必须基于它派生新的子类,才能完成你想要的功能。

同时,Foundation framework里提供了两种常用的NSOperation子类——NSInvocationOperationNSBlockOperation,可供直接使用于代码中。

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里。

若提交一个非并发的operationoperation queue里,queue自己会为你创建一个线程来执行,因此,事实上相对于其它operation这仍是异步的(除非operation之间有依赖关系)。

二、Operation Queues

基于Objective-C,因此基于Cocoa的应用通常会使用Operation Queues

 

1operation 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

 

2operation对象定期地调用 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 observingKVO)依从于以下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都有一定的工作量可以执行。例如100operations执行100次相同任务,可以考虑换成10operations,每个执行10次。

你同样要避免向一个queue中添加过多的operations,或者持续快速地向queue中添加operation,超过queue所能处理的能力。这里可以考虑分批创建operations对象,在一批对象执行完之后,使用completion block告诉应用创建下一批operations对象。

 

9、执行Operations 

1)添加OperationsOperation 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中操作的执行。

 

 

三、Dispatch Queues

1、简介

1GCD提供了几种dispatch queues

aSerial(串行):也称为private dispatch queue,每次只执行一个任务,按任务添加顺序执。可以创建任意数量的串行queues,虽然每个queue本身每次只能执行一个任务,但是各个queue之间是并发执行的

 

bConcurrent(并发):也称为global dispatch queue,可以并发执行一个或多个任务,但是任务仍然是以添加到queue的顺序启动。每个任务运行于独立的线程中,dispatch queue管理所有线程。同时运行的任务数量随时都会变化,而且依赖于系统条件。你不能创建并发dispatch queues。相反应用只能使用三个已经定义好的全局并发queues?

 

cMain dispatch queue全局可用的串行queue,在应用主线程中执行任务。这个queue与应用的?run loop 交叉执行由于它运行在应用的主线程,main queue通常用于应用的关键同步点。虽然你不需要创建main dispatch queue,但你必须确保应用适当地回收。

 

2dispatch queues的几个关键点:

adispatch queues相对其它dispatch queues并发地执行任务,串行化任务只能在同一个dispatch queue中实现。

b)系统决定了同时能够执行的任务数量,应用在100个不同的queues中启动100个任务,并不表示100个任务全部都在并发地执行(除非系统拥有100或更多个核)

c)系统在选择执行哪个任务时,会考虑queue的优先级。

dqueue中的任务必须在任何时候都准备好运行,注意这点和Operation对象不同。

eprivate dispatch queue是引用计数的对象。你的代码中需要retain这些queue,另外dispatch source也可能添加到一个queue,从而增加retain的计数。因此你必须确保所有dispatch source都被取消,而且适当地调用release

 

2Queue相关的技术

1Dispatch group

用于监控一组block对象完成(你可以同步或异步地监控block)。Group提供了一个非常有用的同步机制,你的代码可以等待其它任务的完成

2Dispatch semaphore

类似于传统的semaphore(信号量),但是更加高效。只有当调用线程由于信号量不可用,需要阻塞时,Dispatch semaphore才会去调用内核。如果信号量可用,就不会与内核进行交互。使用信号量可以实现对有限资源的访问控制

3Dispatch source

Dispatch source在特定类型的系统事件发生时,会产生通知。你可以使用dispatch source来监控各种事件,如:进程通知、信号、描述符事件、等等。当事件发生时,dispatch source异步地提交你的任务到指定的dispatch queue,来进行处理。

 

3、使用Block实现任务

设计Block时需考虑以下关键指导方针:

1)对于使用dispatch queue的异步Block,可以在Block中安全地捕获和使用父函数或方法中的scalar变量。但是Block不应该去捕获大型结构体或其它基于指针的变量,这些变量由Block的调用上下文分配和删除。在你的Block被执行时,这些指针引用的内存可能已经不存在。当然,你自己显式地分配内存(或对象),然后让Block拥有这些内存的所有权,是安全可行的。

2Dispatch queue对添加的Block会进行复制,在完成执行后自动释放。换句话说,你不需要在添加BlockQueue时显式地复制

3)尽管Queue执行小任务比原始线程更加高效,仍然存在创建Block和在Queue中执行的开销。如果Block做的事情太少,可能直接执行比dispatchqueue更加有效。使用性能工具来确认Block的工作是否太少

4)绝对不要针对底层线程缓存数据,然后期望在不同Block中能够访问这些数据。如果相同queue中的任务需要共享数据,应该使用dispatch queuecontext指针来存储这些数据。

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

adispatch_get_current_queue:作为调试用途,或者测试当前queue的标识。在block对象中调用这个函数会返回block提交到的queue(这个时候queue应该正在执行中)。在block对象之外调用这个函数会返回应用的默认并发queue

bdispatch_get_main_queue:函数获得应用主线程关联的串行dispatch queueCocoa应用、调用了 dispatch_main 函数或配置了run loopCFRunLoopRef类型或一个 NSRunLoop对象)的应用,会自动创建这个queue

cdispatch_get_global_queue:来获得共享的并发queue

 

4Dispatch 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是你dispatchqueue的另一段代码,在原始任务完成时自动执行。

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内存管理机制,因此可以在提交到queueblock中自由地使用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就保持挂起状态。因此你必须对应地调用suspendresume函数。

 

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);

 

 

9Dispatch Queue和线程安全性

使用Dispatch Queue实现应用并发时,也需要注意线程安全性:

aDispatch queue本身是线程安全的。换句话说,你可以在应用的任意线程中提交任务到dispatch queue,不需要使用锁或其它同步机制。

b)不要在执行任务代码中调用 dispatch_sync函数调度相同的queue,这样做会死锁这个queue。如果你需要dispatch到当前queue,需要使用 dispatch_async 函数异步调度

c)避免在提交到dispatch queue的任务中获得锁,虽然在任务中使用锁是安全的,但在请求锁时,如果锁不可用,可能会完全阻塞串行queue。类似的,并发queue等待锁也可能阻止其它任务的执行。如果代码需要同步,就使用串行dispatch queue

d)虽然可以获得运行任务的底层线程的信息,最好不要这样做。

 

四、Dispatch Sources

1、关于Dispatch Source

dispatch source是基础数据类型,协调特定底层系统事件的处理。GCD支持以下dispatch source

Timer dispatch source:定期产生通知

Signal dispatch sourceUNIX信号到达时产生通知

Descriptor dispatch source:各种文件和socket操作的通知

数据可读

数据可写

文件在文件系统中被删除、移动、重命名

文件元数据信息改变

Process dispatch source:进程相关的事件通知

当进程退出时

当进程发起forkexec等调用

信号被递送到进程

Mach port dispatch sourceMach相关事件的通知

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 portdispatch 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

 

5Dispatch 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继续。但是不会递送所有事件,而是先合并到单一事件,然后再一次递送。例如你监控一个文件的文件名变化,就只会递送最后一次的变化事件。

 

原创粉丝点击