iOS并发编程(上)-- NSOperation queue

并发是多件事情同时发生的一个概念。
在过去,在应用中引入并发就需要创建一个或者多个额外的线程。不幸的是,写线程代码是一个挑战。线程是低层次工具必须手动的管理。给定应用可选的线程数会基于当前系统负载和硬件动态的改变。实现一个正确的线程方案变得额外的困难,很难达到。并且,线程同步机制的使用会增加软件的复杂度和风险而且还不能保证提升性能。

iOS和OS X都提供了一个比传统基于线程更加异步的方式给多任务的执行。不用直接创建线程,应用只需要指定任务并且让系统执行他们。开发者获取了更加简单和高效的编程模型。

尽管线程已经使用了多年并且会继续发挥作用,他们不再解决一般问题的多任务了。使用线程时,创建一个可衡量的解决方案在你的肩上。你需要决定创建多少个线程并且随着系统条件的变化调整线程数量。另外一个问题是你的程序承担大多数线程创建和维护的开销。

为了替换直接依赖线程,OS X 和iOS采用了一个异步设计方式来解决并发问题。异步函数引入操作系统多年了并且经常使用来执行花长时间运行的任务,比如从磁盘读取数据。当调用时,一个异步函数在后台做这些任务并且在完成后返回。典型的,这个工作涉及到一个后台线程,在线程上开始这个工作,完成以后发送一个通知给调用方。过去,如果你希望做的事情的异步函数不存在,你必须写你自己的异步函数然后创建你的线程。现在IOS和OS X提供了技术允许你来执行任何异步任务不需要自己管理线程。

其中一个技术执行异步任务的是Grand Central Dispatch(GCD).这个技术把你通常需要写在你应用中的线程管理代码交给了系统层面。所有你需要做的就是定义你希望执行的任务把他们添加到一个合适的dispatch queue上。GCD创建需要的线程来并且将你的任务放在这些线程上执行。因为线程管理是系统的一部分了,GCD提供一个全部的方法来管理任务的执行,提供比传统线程更好的效率。

Operation Queues是Objective-C对象类似于dispatch queues的作用。你定义你的任务然后添加任务到operation queue,它处理任务执行的调度。像GCD一样,operation queues处理线程管理的所有事情,确保你的任务在系统上执行得又快又有效率。

Dispatch Queues
Dispatch Queues是一个基于C的机制来执行用户任务。一个Dispatch Queue执行任务可以是串行的也可以是并发的。但是总是先进先出的。一个串行的Dispatch Queue一次只运行一个任务,直到任务完成后再从队列头上取出下一个任务开始执行。相对而言,一个并发执行的Dispatch Queue启动它能启动的尽可能多的任务,不需要等待。

Dispatch Queues有以下优点:
他们提供直接而简单的编程接口
他们提供自动和完全的线程池管理
他们提供协作组件的速度。
他们更加内存高效(因为线程栈不在应用的内存中,而在内核的内存中?)
他们不会导致内核负载过高
他们使用 dispatch queue异步处理任务,不会让queue死锁
他们在竞争中保持平和。
串行dispatch queue提供了一个比锁和其他同步机制更加高效的选择

你提交给dispatch queue的任务必须封装在一个函数或者一个block 对象中。block对象是在ios4中的类似于函数指针但有有额外优点的c语言特性。与其定义blocks在他们自己的词法作用域,你一般定义blocks在他们能访问的一个函数或者方法内部。blocks可以移出他们原始的范围然后复制到堆上,这就是当你将他们提交到dispatch queue上发生的。所有这些语义使得使得少量代码就可以实现非常动态的任务成为可能。

Dispatch queues是GCD技术的一部分和C运行时的一部分。

Dispatch Sources
Dispatch Sources是基于C的机制处理指定类型的系统异步事件。一个dispatch source封装包括特定类型的系统事件的信息然后提交给一个指定的block对象或者函数给dispatch queue当事件发生时。你可以使用dispatch sources来监视以下系统事件
定时器(Timers)
信号处理(Signal handlers)
描述符相关的事件(Descriptor-related events)
进程相关事件(Process-related events)
端口事件(Mach port events)
你触发的用户事件(Custom events that you trigger)

Dispache sources是GCD技术的一部分。

Operation Queues
一个Operation Queue是一个Cocoa等价的并发dispatch queue,它是通过NSOperationQueue类来实现。
dispache queue执行任务是先进先出的,operation queue会考虑其他因素来决定任务的执行顺序。这些因素中的主要因素是给定任务依赖于其他任务的完成。你配置依赖关系当定义你的任务时,然后使用他们来创建负责的执行顺序。

你提交给operation queue的任务必须是NSOperation类的实例。一个operation 对象是一个Objective C的对象封装了你需要执行的工作和你执行时需要的数据。因为NSOperation类本质是一个抽象类,你需要定义自己的子类来执行你的任务。然而,Foundation 框架提供了一些具体的子类你能够创建和使用来执行任务。

Operation Object产生key-value observing KVO通知,这是一个有用的方法来监视你任务的完成情况。尽管operation queues 总是执行operations 并发的,需要时你可以使用依赖关系来确保他们串行的执行。

异步设计技术
在你开始重新设计你的代码来支持并发时,你需要问问你自己是否真的需要。并发能够提高你代码的响应能力来确保主线程空闲的响应用户事件。并发能提高你代码的执行效率,更多的核心执行更多的任务同一时间。然后,它也增加了代码复杂度难以编写和调试。

因为它增加了复杂度,并发不是一个你可以随意加给你的程序特性。正确的增加并发需要仔细的思考你程序执行的任务和执行这些任务需要的数据结构。正确的完成了以后,你可以发现你的代码比之前更加慢并且更少的响应用户了。因此,在开始你的设计时设定一些目标然后思考一些你需要采用的方法是值得的。

每一个应用有不同的需求和不同的需要执行的任务。一个文档不可能完整的告诉你如何设计你的应用和它的任务。然而,以下部分尝试提供一些引导来帮助你在设计过程中做出正确的选择。

定义你的应用程序的预期的行为
在思考给你的程序添加并发之前,你必须先定义你期待你的应用的正确的行为。明白你的应用的预期行为可以为你随后验证你设计提供方法。同时给你了一些主意关于你引入并发带来的预期的执行效果的收益。


什么时候使用线程
尽管operation queues和dispatch queues是执行并发任务的优先选择,但是它们不是万能药。依赖于你的应用,可能有一些任务你需要创建自定义的线程。如果你创建用户线程,你必须努力创建尽可能少的线程并且你必须只有在特殊的任务不能通过另外的方法来实现时使用这些线程。
线程仍然是一种实现实时运行代码的很好的方式。Dispatch queues让每个任务尽可能的快但是它们并不遵守实时运行的约束。如果你需要你的代码运行更加精准的行为,线程仍然是一个更好的选择。
使用线程编程,你必须谨慎的使用线程并且只有在绝对必须的时候才使用。

Operations Queues
Cocoa operations是一个面向对象的方式来封装你需要异步执行的任务。Operations设计的即可以联合一个operation queue使用也可以单独使用。因为它们是基于Objective C,operations是使用在iOS和OS X中基于Cocoa的应用程序中最常见的。

关于Operation Objects
一个operation对象是一个NSOperation类的实例,你用来封装你希望你的应用程序执行的任务。NSOperation类本身是一个抽象基类必须进行子类化来做一些有用的工作。尽管是抽象的,这个类提供了一些重要的基础概述来最小化你的子类需要做的工作。另外,基础框架提供了两个具体化的子类你可以使用。


所有的operation对象支持下面的关键特性:
1. 支持建立与其他operation对象的依赖关系。这些依赖关系阻止一个operation运行直到所有的它依赖的operations完成运行。
2. 支持一个可选的完成block,在operation的主任务完成后执行。
3. 支持通过KVO通知来监视你的operations的执行状态的变化
4. 支持优先级的operations并且因此影响它们的执行顺序
5. 支持退出语法来允许你停止一个正在运行的operation。

operations的设计可以帮助你提供你的应用的并发水平。Operations同样是一个组织和封装你的程序行为到简单的分离块的方式。替代运行一些代码在你程序的主线程,你可以将它们提交一个或者多个operation对象到一个queue,然后让相应的工作在一个或者多个分离的线程上异步的执行。

并发和非并发的Operations
尽管你通常执行operations是将它们添加到一个operation queue中,这并不是必须的。可以通过调用它的start方法来手动的执行一个operation对象,但是这样做不能保证这个operation与其他的代码并行运行。在start方法调用后,operation类的isConcurrent方法告诉你一个operation是同步运行还是异步运行的。这个方法默认是返回NO,意味着operation在它的调用线程中是同步运行的。

如果你希望实现一个并发的operation,它在调用线程中异步运行,你必须写一些额外的代码来启动operation异步的。比如,你可能分裂一个单独的线程,调用一个异步的系统函数,或者做一些确保start方法启动任务并且在任务完成之前立即返回,在所有的可能中。

大部分开发者不需要实现一个并发的operation对象。如果你通常将你的operations添加到一个operation queue中,你不需要实现并发operations。当你提交一个非并发的operation给一个operation queue时,queue本身会创建线程来运行你的operation。这样,添加一个非并发的operation到一个operation queue上仍然达到了异步执行你的operation对象的代码的效果了。定义并发的operations只有在必要的情况下才需要:在不添加它到一个operation queue,你需要异步地执行你的operation。

创建一个NSInvocationOperation对象
NSInvocationOperation类是NSOperation地一个具体地子类,运行时,调用你指定的对象和selector。使用这个类来避免为你程序中每个任务都定义大量的用户operation对象。特别是如果你修改一个已经存在的程序和已经存在的对象和方法需要执行必须的任务。当你希望调用的方法依赖环境能够变化时你可以使用它。比如,你可以使用一个invocation operation来执行一个基于用户输入动态选择的selector。

创建一个invocation operation的过程是直接的。你创建和初始化一个新的实例,传递需要的对象和selector来执行初始化方法。例子如下:
- ( NSOperation  *)taskWithData:( id )data
{
     NSInvocationOperation  *invocationOp = [[ NSInvocationOperation  alloc ]  initWithTarget : self  selector : @selector (myTaskMethod:)  object :data];
     return  invocationOp;
}
- ( void )myTaskMethod:( id )data
{
     NSString  *inputString = data;
     //the actual work you want to do
}

创建一个NSBlockOperation对象
NSBlockOperation类是NSOperation的一个具体的子类,充当一个或者多个block对象的包装器。这个类为已经使用operation queue并且不希望创建dispatch queue的应用程序提供了一个面向对象的包装。你可以使用block operations来获取operation依赖性,KVO通知,和其他的一些特性你不能从dispatch queue获取。

当你创建一个block operation,在初始化时你通常添加至少一个block。你可以根据需要随后添加更多的block。 当需要执行一个NSBlockOperation对象时,对象提交它所有的block到一个默认优先级,并发的dispatch queue。 block对象等待所有block完成执行。当最后一个block完成执行,operation对象标记它自己为完成。这样,你可以使用一个block operation来跟踪一组执行的blocks,就像你使用一个线程join多个线程并且合并它们的结果。不同之处是block operation它自己运行在一个分离的线程上, 在等待block operation完成时 你的应用的其他线程可以继续执行工作。
     NSBlockOperation  *blockOp = [ NSBlockOperation  blockOperationWithBlock :^{
         NSLog ( @"Beginning operation.\n" );
        [ self  myTastInBlockOp ];
    }];
完成block operation对象的创建后,你可以使用addExecutionBlock:方法来添加更多的block。 如果你需要串行的执行block,你需要将它们提交到需要的dispatch queue。这里的意思就是不要使用NSBlockOperation,而是使用GCD?

定义一个自定义的Operation Object
如果block operation和invocation opetation对象都不能满足你的需求,你可以直接子类化NSOperation并且添加你需要的任何行为。NSOperation类提供了一个基本的子类化的线路。operation类提供了大量的基础来处理大部分的 依赖于的KVO通知的 工作。然而,当你需要支持已经存在的基础来而确保你的operations的行为正确。大量的额外的工作你需要做的依赖于你是实现一个并发和还是非并发的operation。

定义一个非并发的operation比一个并发的opertion简单多了。一个非并发地operation,所有你需要做地就是执行你的主要地任务并且正确响应退出事件;已经存在的类的基础为你所了所有其他的事。定义一个并发的operation,你必须用你自己的代码替换已经存在的基础。下面的部分为你展示如何实现这两种对象。

执行主任务
每个operation对象必须至少 实现以下的方法:
1.一个用户自定义的初始化方法
2.main方法
你需要一个自定义的初始化方法将你的operation对象放入一个已知的状态并且用一个自定义的main方法来执行你的任务。你可以根据需要实现额外的方法,当然,就像如下:
1.自定义方法你打算从main方法中调用的。
2.accessor方法来设定和获取操作结果
3.NSCoding协议的方法来允许你序列化和反序列化opertion对象。

下面的代码展现了一个自定义的NSOperation子类的模版。
@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
如何实现一个NSoperation子类的更详细的例子,请看NSOperationSample

响应退出事件
一个operation开始执行之后,它会继续执行它的任务直到它结束或者你的代码显示的退出operation。退出可能会在任何时间段发生,甚至在一个operation开始执行前。尽管NSOpertion类提供给客户端一个方法来退出一个operation,认识到退出事件必要的时候采用。当一个operation被立即的结束了,可能就没有方法来回收已经被分配的资源了。结果是,operation对象希望检查退出事件并且平静的退出当它们发生在operation的中间时。

为了在你的operaion对象中支持退出,所有你必须要做的是在你自定义的代码中间隔地调用对象的isCancelled方法,在isCancelled返回YES时立即返回。支持退出无论如何在你的operation都是很重要的,无论是你直接子类化NSOperation还是使用已经具体化的子类之一。isCancelled方法本身是非常轻量级的并且可以被频繁的被调用不会造成任何重要的性能影响。当设计你operation对象时,你必须考虑调用isCancelled方法在你代码的以下的几个地方:
1.立即调用在你执行任何实质性的工作前
2.在循环的每次迭代中至少调用一次,或者每次迭代时间很长的化多调用几次。
3.你代码的任何地方可能相对来说比较容易中止operation。

下面提供了一个简单的示例在operation对象的main方法中如何响应退出事件,isCancelled方法在每一次循环中都被调用,允许快速退出在任务开始和定期的重复时。
- ( 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.
    }
}

尽管上面的示例没有包含清除代码,你自己的代码必须确保释放任务你自己申请的资源。

配置operation成为并发执行
operation对象执行默认是同步的,它们通过调用start方法在线程上执行自己的任务。因为 operation queues为非并发的 operations 提供线程,所以,大部分 operations仍然是以异步的方式运行。然而,如果你打算手动执行 operations并且仍然希望它们异步运行,你必须采取一些合适的措施来确保这样。你可以通过定义你的 operations对象成一个并发的 operations。


本部分的剩余部分展示了一个示例实现MyOperation类,这个类阐述了实现一个并发的operation基础的代码。MyOperation类简单的在一个它自己创建的分离的线程上执行它自己的main方法。main方法执行的实际的工作是不相关的。这个例子的目的是阐述了基本的构建你需要提供来定义一个并发operation。
下面展示了MyOperation的接口和部分实现。isConcurrent,isExecuting,isFinished方法的实现相对来说很直接。isConcurrent方法必须返回YES来指明它是一个并发的operation。isExecuting和isFinished方法简单的返回存储在实例变量中的值。

@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 ;
}
下面展示MyOperation类的start方法。这个方法的实现是最小化的仅仅为了阐明你必须执行的任务。在这样的情况下,这个方法简单的启动一个新线程并且配置它来调用main方法。这个方法同时更新executing成员变量并且为键路径为isExecuting产生KVO通知来反映这个值的改变。这些工作完成后,简单的返回,留下新分离的线程去执行实际的工作。
- ( 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" ];
}

下面接着展示MyOperation剩下的部分。我们看见main方法是新线程的入口点。它执行operation对象关联的工作并且当工作完成后调用自定义的completeOperation方法。这个completeOperation方法这时为isExecuting和isFinished键路径产生需要的KVO通知来反应operation状态的改变。
- ( 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" ];
}

即使一个operation退出了,你必须总是通知KVO的观察者你的 operation现在完成了它的工作。当一个 operation对象依赖于其他的 operation对象完成时,这个 operation监视这些对象的isFinished键路径。没有成功的产生一个完成通知可能因此阻止你应用中的其他 operations的执行。( Failing to generate a finish notification can therefore prevent the execution of other operations in your application.
 
维持KVO遵从性(Compliance)
operation类为下面这些键路径KVO遵从:
isCancelled;
isConcurrent
isExecuting
isFinished
isReady
dependencies
queuePriority
completionBlock

如果你重写start方法或者为NSOperation对象做一些重要的自定义,你必须确保你自定义的对象为这些键路径仍然是KVO遵从性的。当重写start方法时,键路径中你最需要关注的是isExecuting和isFinished。在重新实现main方式时,它们是键路径中最容易受到影响的。

键路径(key path)
如果你希望实现支持依赖其他operation对象时,你可以重新isReady方法并且强制它返回NO直到你自定义的依赖被满足。(如果你实现自定义的依赖,如果你仍然支持NSOperation类提供的默认的依赖管理系统,确保在你的isReady方法中调用super)当你的operation对象 准备就绪的状态 改变时,为isReady键路径产生KVO通知来报告这些改变。除非你重些addDependency:或者removeDependency:方法,你不用担心为dependencied键路径产生KVO通知。

尽管你可以为NSOperation的其他key path产生KVO通知,你没有必要去做这些。如果你需要退出一个operation,你可以简单的调用已经存在的cancel方法来退出。类似的,你很少需要修改 operation对象中的 queue优先级说明。最后,除非你的 operation能够动态的改变它的并发状态,你不需要为isConcurrent键路径提供KVO通知。

自定义 operation对象的执行行为
operation对象的配置发生在你已经创建了它们之后但是在将它们添加到queue之前。本部分 描述的 配置的类别可以应用在所有的operation对象中,无论你使用已经存在的子类还是自定义NSOperation。

配置相互依赖
依赖你串行执行operation对象的 是一种方式。一个operation依赖其他的operations不能开始执行直到所有它依赖的operations已经完成执行。因此,你可以使用依赖性来创建简单的两个operation对象间一对一的依赖关系或者构建复杂的对象依赖关系。

建立两个operation对象的依赖关系,你可以使用NSOperation的addDependency:方法。这个方法创建了一个单向的依赖从当前的operation对象到你用参数指定的目标operation对象。这个依赖意味着当前的对象不能开始执行直到目标对象完成执行。依赖关系在同一个queue中operations是没有被限制的。( Dependencies are also not limited to operations in the same queue.)operation对象管理它们自己的依赖关系并且它完美的接受在operations中创建依赖关系和添加它们到不同的queues中。有一件事件是不能接受的,就是在operations中创建依赖循环。这样做是一个程序错误组织operations运行。

当一个operation所有的依赖关系都已经完成执行,一个operation对象通常变成准备执行(如果你自定义了isReady方法,operation准备就绪就取决于你自己设定的条件)如果一个operation对象在queue中,这个queue可能开始执行这个operation在任何时间内。如果你打算执行operation手动的,取决于你调用operation的start方法。
重要:你必须总是在运行你的operations或者添加它们到一个operation queue之前配置依赖关系。依赖关系添加以后可能不会阻止一个给定的operation对象执行。

依赖关系依赖于每一个operation对象在对象状态更改时发送合适的KVO通知。如果你自定义自己的operation对象的行为,你可能需要从你自定义的代码中产生正确的KVO通知来避免产生依赖性的问题。

改变一个operation的执行优先级
operation添加到一个queue中,执行顺序的首要决定因素是queue operation的准备就绪和它们相对的优先级。准备就绪取决于一个operation和其他operations的依赖关系,但是优先级是一个operation对象自身的属性。默认的,所有的新的operation对象有一个“normal”优先级,但是你可以添加或者降低这个优先级通过调用对象的setQueuePriority:方法。

优先级层面应用仅仅是operations在同一个operation queue中。如果你的应用程序有多个operation queues,每个它拥有的operations的优先级独立于其他的queues。因此,在不同的queue中,低优先级的operations可能比高优先级的operations先执行。

优先级层面不能代替依赖性。优先级决定一个operation queue开始执行的顺序只有在这些operations现在准备好执行。比如,如果一个queue包含一个高优先级和一个低优先级operation,并且两者都准备好执行,queue首先执行高优先级的operation。然而,如果高优先级的operation没有准备好但是低优先级的准备好了,queue首先执行低优先级的。如果你希望阻止一个operation开始执行直到另外一个operation完成执行,你必须使用依赖性。

更改基本的线程优先级

设置一个完成block
一个operation可以在它的主任务结束执行时执行一个完成block。你可以使用一个完成block做任何你认为不是主任务部分的工作。比如,你可能使用这个block通知感兴趣的客户端你的operation已经执行完了。一个并发的operation对象可能使用这个block来产生它最后的KVO通知。
使用NSOperation的setCompletionBlock:方法来设置一个完成block。你传递给这个方法的block必须是没有参数和没有返回值的。

为operation对象决定一个合适的范围
尽管可以添加一个任意大数量的operations到一个operation queue中,这样做通常是不实际的。就像其他的对象,NSOperation类的实例消耗内存并且它们的执行有真实的开销。如果每个你的operation对象处理少量的工作,你创建了数万个这样的operations,你可能发现你花费了太多的时间调度operations而不是做实际的工作。如果你的应用程序已经受到了内存约束,你可能发现数万的operation对象在内存中甚至降低你的执行效率。

高效的使用operations的关键是站到一个合适的平衡:在你需要做的工作的总和与保持处理器忙碌之间找到平衡。尝试确保你的operations处理适量的工作。比如,如果你的应用创建100个operation对象用100个不同的值来执行同样的任务,考虑每次创建10个operation对象来处理10个值。

你必须避免一次添加大量的operations到一个queue中,或者避免连续的添加operation对象到一个queue中的速度超过它们被处理的速度。一批批的创建这样的对象而不是用operation对象淹没queue。当一批完成了执行,使用一个完成block来告诉你的应用创建一个新的一批。当你有大量工作要做时,你希望保持queue充满足够的operations来让处理器保持忙碌,但是你又不希望一次创建过多的operations导致你的程序耗尽内存。

当然,你创建的operation对象的数量,你在每一个operation上执行的工作的总量,是易变的和完全的依赖于你的应用。你必须经常使用如instruments和Shark这样的工具来找到一个效率和速度之间合适的平衡。(performance overview)

执行operations
最后,你的应用需要执行operations来做关联的工作。在本部分,你可以学习几种方式来执行operations,你如何能在runtime时操作你的operations的执行。

添加operations到一个operation queue
目前为止,执行operations最简单的方式就是使用一个operation queue,是NSOperationQueue的一个实例。你的应用负责创建和保持任何它打算使用的operation queues。一个应用可以拥有任意数量的queues,但是在同一个时间点执行的operations有实际的限制。operation queue和系统限制并发的operation数量为一个值,这个值对于CPU的可用核心数和系统负载有关。因此,创建额外的queues并不意味着你可以执行额外的operations。

为了创建一个queue,你在你应用中为它像其他对象一样分配内存:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
添加operations到queue中,你可以使用addOperation:方法。在OS X v10.6和以后,你可以使用addOperations:waitUntilFinished:添加operations组,或者你可以使用addOperationWithBlock:方法添加block对象到queue中。每个方法都向队列中加了一个operation并且通知queue必须准备处理它们。在大部分情况下,operations在被添加到queue中不久后就会被执行,但是operation queue可能会因为一些原因延迟加入到队列的operations的执行。特别是,执行可能会被推迟如果加入队列的operation依赖于其他的operations并且还没有完成。执行可能会被推迟如果operation queue自己被暂停了或者它已经执行了它的最大数量的并发operations。如下实例展示了添加operations到一个queue中的基本的语法。
[aQueue addOperation:anOp];  // Add a single operation
[aQueue addOperations:anArrayOfOps waitUntilFinished: NO ];  // Add multiple operations [aQueue addOperationWithBlock:^{
/* Do something. */
}];
重要:永远不要修改一个operation对象在它已经被添加到queue中以后。当在queue中等待时,operation可能在任何时候开始执行,所以改变它的依赖关系或者它包含的数据可能造成不利的影响。如果你希望直到 operation的状态,你可以使用NSOperation类的方法来确定 operation是否在运行,等待运行或者已经结束了。

尽管NSOperationQueue类是为并发执行 operations而 设计的,它可以强制一个queue同一时间只运行一个 operation。setMaxConcurrentOperationCount:方法让你可以配置operation queue最大的并发 operation数量。把1传递给这个函数会导致queue一次只执行一个 operation,尽管一次只执行一个 operation,执行的顺序依然是基于其他因素的,比如每个 operation的准备就绪和它们被赋予的优先级。然而,一个串行的 operation queue的行为和一个串行的GCD dispatch queue不完全一样。如果你的 operation对象 执行顺序对你很重要的话,你必须在添加到 operation queue之前 使用依赖关系来建立执行顺序。

手动执行 operations
尽管 operation queues是运行 operation对象最方便的方式,也可以运行 operations不通过queue。如果你选择手动运行 operation,然而,你的代码中有一些需要主要的地方。特别是, operation必须准备好运行并且你使用start方法来启动 operation。

operation不能运行直到它的isReady方法返回YES。isReady方法已经被NSOperation类集成到依赖管理系统来提供 operation依赖关系的状态。只有当它的依赖关系都清除了 operation才能自由的开始执行。

当手动执行一个 operation时,你需要使用start方法来启动运行,你使用这个方法,而不是main或者其他方法,因为start方法在实际执行你的自定义代码前执行一些安全检查。更详细的,默认的start方法产生 operations需要正确处理它的依赖关系的 KVO通知。这个方法正确的避免执行已经退出的 operation并且在 operation没有真正准备好运行时抛出一个异常。

如果你的应用定义一个并发的operation对象,你必须考虑在启动它之前优先调用 operation的 isConcurrent方法。当这个方法返回NO时,你的局部的代码可以决定是在当前线程中同步的执行 operation还是首先创建一个分离的线程。实现这种类型的检查完全取决于你。

下面的示例展示了这种类型的简单检查。如果返回NO,你可以安排一个定时器稍后调用这个方法。你可以保持这样的调度一直到这个方法返回YES,可能会发生因为 operation已经退出了。
- ( 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 ])
    {
        [ self  willChangeValueForKey : @"isFinished" ];
        [ self  willChangeValueForKey : @"isExecuting" ];
         _executing  =  NO ;
         _finished  =  YES ;
        [ self  didChangeValueForKey : @"isExecuting" ];
        [ self  didChangeValueForKey : @"isFinished" ];
        ranIt =  YES ;
    }
     return  ranIt;
}

退出operations
一旦加入到 operation queues, operation对象实际就是属于queue了并且不能退出。退出queue的唯一方法就是cancel  operation。你可以通过调用cancel方法退出一个单独的 operation对象或者你可以调用cancelAllOperations方法从queue种退出所有的 operation对象。

你只有在确信不再需要这个 operation时才应该退出它。发出一个退出命令将operation对象置为退出状态,会阻止operation的执行。因为一个退出的operation仍然可以被认为是完成的,依赖它的对象会收到一个正确的KVO通知来清除依赖关系。因此,响应一些重大事件时更加常见的做法是退出所有加入到queue中的operations, 比如应用程序的退出或者用户发出了退出请求,而不是选择性的退出operations。

等待Operations的完成
为了达到最好的性能,你应该尽可能的把你的operations设计成异步的,当operation在执行时释放你的应用做更多的工作。如果创建operation的代码同时处理operation的结果,你可以使用NSOperation的waitUtilFinished方法来阻塞代码直到operation完成。通常来说,最好避免调用这个方法如果你能帮助它的话。阻塞当前线程可能是一个方便的解决方案,但是它引入了更多的串行到你的代码中并且限制了并发的数量。
important:你应该永远不要在你应用的主线程中等待一个operation。你只应该在分离的线程中或者另一个operation中做。阻塞你的主线程阻止你的应用响应用户事件和导致你的应用出现不响应的情况。
除了等待一个单一的operation完成,你也可以使用NSOperationQueue的waitUtilAllOperationAreFinished方法来等待队列中所有的 operations的完成。当等待一个完整的队列的完成时,注意你应用程序的其他线程仍然可以添加operations到队列中,因此可以延长等待。

暂停和恢复Queues
如果你希望暂时停止operations的 执行 ,你可以使用setSuspended:方法暂停对应的operation queue。暂停一个queue不会造成已经在运行的operations停止在它们的任务的中间。它仅仅阻止新的operations被安排执行。你可能暂停一个queue来响应用户请求暂停所有要进行的工作,因为期望的是用户可能最终希望恢复这个工作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值