多线程开发是一件需要特别精心的事情,即使是对有多年开发经验的工程师来说。
为了能让初级开发工程师也能使用多线程,同时还要简化复杂性。各种编程工具提供了各自的办法。对于iOS来说,建议在尽可能的情况下避免直接操作线程,使用比如NSOperationQueue这样的机制。
可以把NSOperationQueue看作一个线程池,可往线程池中添加操作(NSOperation)到队列中,一旦一个操作被加入队列,该队列就会启动并开始处理它。一旦该操作完成,队列就会释放它。即队列会在NSOperation的main方法结束后自动释放该对象。
由于很难在队列中加入足够多的任务使得我们看到它们是并发运行的。但如果你的任务需要更多的时间,你就可以看到队列是同时运行所有的任务。如果你想限制并行运行的任务数目,你可以设置线程池中线程的并发处理个数setMaxConcurrentOperationCount:。若设置为一个线程,各个操作就可以认为是近似的顺序执行了。为什么说是近似呢,后面会做解释。
编写最简单的示例
先写个最简单的示例。
编写一个NSOperation的子类,只需实现main方法,main方法是每个线程执行时的入口函数。这里非常类似Java的Thread,你可以继承它,并覆盖Thread的run方法,在该方法里面写入需要执行的代码。这里的main方法和run方法作用是相似的。
头文件:
这里的operationId属性不是必须的,是我想在后面标识区分多个Task的标识位。
m文件:
这里模拟了一个耗时10秒钟的操作。
下面需要把Task加入到队列中:
我直接找了个Controller的方法写上了。运行结果是,界面出现了,而task还未执行完,说明是多线程的。10秒钟后,日志打印完毕,类似这样:
可以向操作队列(NSOperationQueue)增加多个操作,比如这样:
那么打印出的内容是不定的,有可能是这样:
甚至有可能是这样:
因为两个操作提交的时间间隔很近,线程池中的线程,谁先启动是不定的。
那么,如果需要严格意义的顺序执行,怎么办呢?
处理操作之间的依赖关系
如果操作直接有依赖关系,比如第二个操作必须等第一个操作结束后再执行,需要这样写:
这样,即使是多线程情况下,可以看到操作是严格按照先后次序执行的。
控制线程池中的线程数
可以通过类似下面的代码:
来设置线程池中的线程数,也就是并发操作数。默认情况下是-1,也就是没有限制,同时运行队列中的全部操作。
需要注意的一个问题:
在多线程操作中,有一个著名的错误,叫做“Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread”,一旦出现这个错误,程序会立即crashed。
这是由于,apple不允许程序员在主线程以外的线程中对ui进行操作。
而笔者在一次http异步操作中也出现过这个错误。当时使用了NSOperation进行了http异步请求,然后使用kvo模式注册观察者,当数据下载完毕后,在主线程中接收下载完毕的通知,并在observeValueForKeyPath方法中使用[tableview reloadData]更新UI。
这样也导致了上述错误。
解决的方法是使用performSelectorOnMainThread进行ui的更新:
[object performSelectorOnMainThread:@selector(refresh) withObject:nil waitUntilDone:NO];
即在主线程中执行[object refresh]这个函数;