IOS并发编程

ios的多线程管理有3种方式

  1. NSThread & Run Loop
  2. NSOperation
  3. GCD

Run Loop

首先我们说一下线程的起用和退出的问题,当我们自己创建一个线程并分配给它活干的时候,它会立刻开始给我们干活,一旦活干完了,它又没有马上找到新活,那么就会立刻退出,这个线程就结束了。注意,这里是它一旦发现自己没活可干,就会马上消失,片刻都不会停留。

这样我们就遇到一个问题,如果我们打算让这个线程做一个延时的任务,或者想让它接受其它的回调命令,或者等待一个点击等非即时性命令,而这个线程是不知道等的,因为它一发现自己没活干,就消失了。这显然不是我们希望的。可能有人会问,主线程不会这样啊,原因就是我们要讨论的主题,主线程默认开启了RunLoop,而我们创建的线程默认是没有开启的。

因此,如果我们要在非主线程执行一些非即时性的事情,就必须手动开启RunLoop,它一旦开启,线程就会开启监听状态,这样线程便不会退出,而是转入休息状态,RunLoop负责把风,一旦发现活来了,就通知线程开始干活。如果我们确认这个线程再也不需要在处理任何非即时性事件时,可以停止RunLoop,这时候线程就再看看手头有没有现活,有继续做,没有就立刻退出。

输入源传递异步消息给相应的处理例程,并调用runUntilDate:方法来退出(在线程里面相关的NSRunLoop对象调用)。定时源则直接传递消息给处理例程,但并不会退出run loop。

(未完待续,这块还是个半吊子。。。。。。)

NSThread

NSThread 比其他两种方式轻量级,但需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁也会有一定的系统开销。

使用NSThread来创建线程有多个方法:

  1. 使用 + (void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument 类方法来生成一个新的线程。
  2. 使用 - (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument 创建一个新的 NSThread 对象,并调用它的 start 方法。
  3. 调用NSObject的+performSelectorInBackground:withObject:方法生成子线程。
  4. 创建一个NSThread子类,然后调用子类实例的start方法。

编写你线程的主体入口点的额外步骤:

  1. ARC这篇文章里面提到了,有3种情况应该自己创建autorelease池,其中一种情况就是创建了子线程。
  2. 子线程应该自行管理好抛出异常,如果不在子线程内部设置异常处理函数,会导致程序直接Crash。
  3. 如果子线程不止一次操作,需要循环处理的话,设置一个Run Loop。

每个线程都维护了一个键-值的字典,它可以在线程里面的任何地方被访问。你可以使用该字典来保存一些信息,这些信息在整个线程的执行过程中都保持不变。你使用NSThread的threadDictionary方法来检索一个NSMutableDictionary对象,你可以在它里面添加任何线程需要的键。

你创建的任何线程默认的优先级是和你本身线程相同。你可以使用NSThread的 setThreadPriority:类方法来设置当前运行线程的优先级。

退出一个线程推荐的方法是让它从主题入口点正常退出。对于直接杀死线程,这是完全不鼓励的,这样无法保证线程当前使用资源被清理干净了。如果需要在操作中间中断一个线程,建议使用Run Loop的输入源来接收取消消息。

NSOperation

NSOperation本身是抽象基类,我们必须实现子类。Foundation framework提供NSInvocationOperation和NSBlockOperation两个具体子类,你可以直接使用。

所有 operation objects 都支持以下关键特性:

  1. 支持建立基于图的operation objects依赖。可以阻止某个operation 运行,直到它依赖的所有 operation 都已经完成。
  2. 支持可选的 completion block,在 operation 的主任务完成后调用。
  3. 支持应用使用 KVO 通知来监控 operation 的执行状态。
  4. 支持 operation 优先级,从而影响相对的执行顺序。
  5. 支持取消,允许你中止正在执行的任务。

创建一个 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

创建一个NSBlockOperation 对象

NSBlockOperation* theOp = [NSBlockOperation blockOperationWithBlock: ^{
    NSLog(@"Beginning operation.\n");
    // Do some work.
}];

使用addExecutionBlock:可以添加更多block到这个block operation对象。如果需要顺序地执行 block,你必须直接提交到所需的dispatch queue。

自定义operation需要实现的方法:

  1. 自定义initialization方法:初始化,将operation对象设置为已知状态(必须实现)
  2. 自定义main方法:执行你的任务(必须实现)
  3. main方法中需要调用的其它自定义方法
  4. Accessor方法:设置和访问operation对象的数据
  5. NSCoding协议的方法:允许operation对象archive和unarchive

operation响应取消,调用isCancelled:方法,如果返回YES(表示已取消),则立即退出执行。以下地方可能需要调用 isCancelled:g

  • 在执行任何实际的工作之前
  • 在循环的每次迭代过程中,如果每个迭代相对较长可能需要调用多次
  • 代码中相对比较容易中止操作的任何地方

Operation对象默认按同步方式执行,也就是在调用start方法的那个线程中直接执行。但是如果你希望异步执行操作,你就必须定义operation对象为并发操作来实现。

  • start (必须)所有并发操作都必须覆盖这个方法,以自定义的实现替换默认行为。手动执行一个操作时,你会调用start方法。因此你对这个方法的实现是操作的起点,设置一个线程或其它执行环境,来执行你的任务。你的实现在任何时候都绝对不能调用super
  • main (可选)这个方法通常用来实现operation对象相关联的任务。尽管你可以在start方法中执行任务,使用main来实现任务可以让你的代码更加清晰地分离设置和任务代码。
  • isExecuting isFinished (必须)并发操作负责设置自己的执行环境,并向外部 client 报告执行环境的状态。因此并发操作必须维护某些状态信息,以知道是否正在执行任务,是否已经完成任务。使用这两个方法报告自己的状态。这两个方法的实现必须能够在其它多个线程中同时调用。另外这些方法报告的状态变化时,还需要为相应的key path产生适当的KVO通知。
  • isConcurrent (必须)标识一个操作是否并发 operation,覆盖这个方法并返回 YES。

使用 NSOperation 的addDependency:方法在两个operation对象之间建立依赖关系。表示当前operation 对象将依赖于参数指定的目标 operation 对象。如果你自定义了 operation 对象的行为,就必须在自定义代码中生成适当的 KVO 通知,以确保依赖能够正确地执行。

operation 可以在主任务完成之后执行一个 completion block。你可以使用这个 completion block 来执行任何不属于主任务的工作。

执行Operations有2种方法:

  1. 执行 Operations 最简单的方法是添加到 operation queue
  2. 手动执行 Operation,要求 Operation 已经准备好,isReady返回 YES,此时你才能调用start方法来执行

NSOperation的官方例程

GCD

GCD dispatch queues 是执行任务的强大工具,允许你同步或异步地执行任意代码block。

目前GCD中有三种类型的Dispatch Queue:

  1. Main Queue:关联到主线程的队列,可以使用函数dispatch_get_main_queue()获得,加到这个队列中的工作都会分发到主线程运行。主线程只有一个,因此很明显这个是串行队列,每次运行一个工作。
  2. Global Queue:全局队列是并发队列,又根据优先级细分为高优先级、默认优先级和低优先级三种。通过 dispatch_get_global_queue 加上优先级参数获得这个全局队列,例如 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
  3. 自定义Queue:自己创建一个队列,通过函数 dispatch_queue_create 创建,例如 dispatch_queue_create("com.kiloapp.test", NULL) 。第一个参数是队列的名字,Apple建议使用反DNS型的名字命名,防止重名;第二个参数是创建的queue的类型,iOS 4.3以前只支持串行,即DISPATCH_QUEUE_SERIAL(就是NULL),iOS4.3以后也开始支持并行队列,即参数DISPATCH_QUEUE_CONCURRENT。

添加单个任务到Queue:

  • dispatch_asyncdispatch_async_f 函数异步添加
  • dispatch_syncdispatch_sync_f 函数同步添加
  • dispatch_after 同步延迟添加
ios

Comments