iOS里多线程有四种解决方案
1.Pthreads
2.NSThread
3.GCD
4.NSOperation & NSOperationQueue
1.Pthreads
这是一套通用的的解决方案,因为这是符合POSIX标准的线程,基于C语言,在类Unix操作系统(Unix...Linux...Mac OS X)都使用了Pthreads作为操作系统的线程。移植性强,通用。
2.NSThread
这是经过苹果公司封装后,具备面向对象思想的多线程解决方案。可以直接操控线程对象,但是生命周期仍然需要我们自己手动管理,在开发过程中我们会经常使用NSTread.currentThread()来获取当前的线程对象,调试方便。
Swift
let thread = NSThread(target: self, selector: "threadTask", object: nil)
thread.start()
func threadTask() {
NSLog(NSThread.currentThread())
}
在OC里performSelector:这个方法因为被苹果认为不安全,因此在Swift里去掉了。
原因是在假如Selector是在运行时动态选择的话,编译器并不了解调用的方法是否有返回值,因此并能不能运用ARC的内存规则来判断返回值是否应该释放,ARC采用谨慎的做法-不添加释放操作,这样可能会导致内存泄漏。
然而这个解决方案并不够智能,接下来的GCD和NSOperation & NSOperationQueue是我们在项目里用得比较多的。
3.GCD
GCD-Grand Central Dispatch.是苹果为多核的并行运算提出的解决方案,所以会自动合理地利用更多的CPU内核(比如双核、四核),最重要的是它会自动管理线程的生命周期(创建线程、调度任务、销毁线程),完全不需要我们管理,我们只需要告诉干什么就行。同时它使用的也是 c语言,不过由于使用了 Block(Swift里叫做闭包)。
GCD中有两个重要的概念:任务和队列
任务:
即你想要添加的操作,在GCD中用Block表示。任务有两种执行的方式:同步执行和异步执行,区别在于会不会阻塞当前线程直到Block任务执行完成。
同步执行:会阻塞当前进程并等待Block中的任务执行完成,然后当前线线程彩绘继续往下运行。
异步执行:当前线程继续往下执行,不会阻塞当前线程。
队列:
串行队列:根据队列中定义的fifo执行,先进先出的顺序。
并行队列:放到并行队列的任务,GCD 也会 FIFO的取出来,但不同的是,它取出来一个就会放到别的线程,然后再取出来一个又放到另一个的线程。这样由于取的动作很快,忽略不计,看起来,所有的任务都是一起执行的。不过需要注意,GCD 会根据系统资源控制并行的数量,所以如果任务很多,它并不会让所有任务同时执行。
然后组合起来就是:
同步执行 异步执行
串行队列 当前线程,一个一个执行 其他线程,一个一个执行
并行队列 当前线程,一个一个执行 开很多线程,一起执行
创建队列:
主队列:特殊的串行队列,负责所有UI的刷新工作。
Swift
let queue = ispatch_get_main_queue()
自己创建的队列
Swift
let queue = dispatch_queue_create("whatNameYouLike", nil);
let queue = dispatch_queue_create("whatNameYouLike", DISPATCH_QUEUE_SERIAL)
//并行队列
let queue = dispatch_queue_create("whatNameYouLike", DISPATCH_QUEUE_CONCURRENT)
全局并行队列
这是系统提供的一个并发队列。
Swift
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
创建任务:
同步任务:阻塞当前线程(SYNC)
Swift
let queue = dispatch_queue_create("whatNameYouLike", DISPATCH_QUEUE_CONCURRENT)
dispatch_sync(queue) { () -> Void in
NSLog(NSThread.currentThread())
}
异步任务:不会阻塞当前线程(ASYNC)
Swift
let queue = dispatch_queue_create("whatNameYouLike", DISPATCH_QUEUE_CONCURRENT)
dispatch_async(queue) { () -> Void in
NSLog(NSThread.currentThread())
}
队列组: 是GCD的一项特性,能够把任务分组。调用者可以等待这组任务执行完成,也可以在提供回调函数之后继续往下执行,这组任务完成时,调用者会得到通知。其中最值得注意的用法,就是把将要并罚执行的多个任务合为一组,于是调用者就可以知道任务何时全部完成。
Swift
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
let group = dispatch_group_create()
for index in 1...3 {
dispatch_group_async(group, queue, { () -> Void in
for i in 1...3 {
NSLog("group-\(index)-\(i) - \(NSThread.currentThread())")
}
})
}
// 需要等待
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
// 假如不需要等待,完成后自动通知
dispatch_group_notify(group, dispatch_get_main_queue()) { () -> Void in
NSLog("完成 - \(NSThread.currentThread())")
}
栅栏:
在队列中,栅栏块必须单独执行,不能与其他块并行。这仅对并行队列是有意义的,因为串行队列总是按顺序逐个执行。并行队列如果发现接下来要处理的块是栅栏块,就会一直等待前面的并发块都执行完毕,才会单独执行这个栅栏块。
Swift
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
var some:String = ""
func getSome() {
dispatch_sync(queue) { () -> Void in
self.some = _some
}
}
func setSome() {
dispatch_barrier_async(queue) { () -> Void in
_some = self.some
}
}
4.NSOperation & NSOperationQueue
NSOperation 是苹果公司对 GCD 的封装,Objective-C的对象,完全面向对象,所以使用起来更好理解。 大家可以看到 NSOperation 和 NSOperationQueue 分别对应 GCD 的 任务 和 队列 。操作步骤也很好理解:
1.将要执行的任务封装到一个 NSOperation 对象中。
2.将此任务添加到一个 NSOperationQueue 对象中。
添加任务:
NSOperation 只是一个抽象类,所以不能封装任务。但它有 2 个子类用于封装任务。分别是:NSInvocationOperation 和 NSBlockOperation 。创建一个 Operation 后,需要调用 start 方法来启动任务,它会 默认在当前队列同步执行。当然你也可以在中途取消一个任务,只需要调用其 cancel 方法即可。
NSInvocationOperation : 需要传入一个方法名。
值得一提的是,这个方法在Swift里被认为是不安全的,理由同performSelector:方法。
NSBlockOperation : 需要传入一个Block。
Swift
let opr = NSBlockOperation { () -> Void in
NSLog("Inside block operation -- \(NSThread.currentThread())")
}
opr.start()
NSBlockOperation 还有一个方法:addExecutionBlock: ,通过这个方法可以给 Operation 添加多个执行 Block。这样 Operation 中的任务 会并发执行,它会 在主线程和其它的多个线程 执行这些任务。
Swift
let opr = NSBlockOperation { () -> Void in
NSLog("Inside block operation -- \(NSThread.currentThread())")
}
for i in 1...5 {
opr.addExecutionBlock({ () -> Void in
NSLog("\(i) - \(NSThread.currentThread())")
})
}
opr.start()
自定义Operation:
除了上面的两种 Operation 以外,我们还可以自定义 Operation。自定义 Operation 需要继承 NSOperation 类,并实现其 main() 方法,因为在调用 start() 方法的时候,内部会调用 main() 方法完成相关逻辑。所以如果以上的两个类无法满足你的欲望的时候,你就需要自定义了。你想要实现什么功能都可以写在里面。除此之外,你还需要实现 cancel() 在内的各种方法。
创建队列:
我们可以调用一个 NSOperation 对象的 start() 方法来启动这个任务,但是这样做他们默认是 同步执行 的。就算是 addExecutionBlock 方法,也会在 当前线程和其他线程 中执行,也就是说还是会占用当前线程。这是就要用到队列 NSOperationQueue 了。而且,按类型来说的话一共有两种类型:主队列、其他队列。只要添加到队列,会自动调用任务的 start() 方法
主队列:
每套多线程方案都会有一个主线程(当然啦,说的是iOS中,像 pthread 这种多系统的方案并没有,因为 UI线程 理论需要每种操作系统自己定制)。这是一个特殊的线程,必须串行。所以添加到主队列的任务都会一个接一个地排着队在主线程处理。
Swift
NSLog("\(NSOperationQueue.mainQueue())")
其他队列:
因为主队列比较特殊,所以会单独有一个类方法来获得主队列。那么通过初始化产生的队列就是其他队列,会在其他线程异步执行。因为只有这两种队列,除了主队列,其他队列就不需要名字了。
Swift
let queue = NSOperationQueue()
let opr = NSBlockOperation { () -> Void in
NSLog("---- \(NSThread.currentThread())")
}
// 添加其他block
for i in 1...3 {
opr.addExecutionBlock { () -> Void in
NSLog("----\(i)---- \(NSThread.currentThread())")
}
}
queue.addOperation(opr)
将 NSOperationQueue 与 GCD的队列 相比较就会发现,这里没有串行队列,那如果我想要10个任务在其他线程串行的执行怎么办?
这就是苹果封装的妙处,你不用管串行、并行、同步、异步这些名词。NSOperationQueue 有一个参数 maxConcurrentOperationCount 最大并发数,用来设置最多可以让多少个任务同时执行。当你把它设置为 1 的时候,相当于是串行。
NSOperationQueue 还有一个添加任务的方法,- (void)addOperationWithBlock:(void (^)(void))block; ,这是就和GCD类似了。这样就可以添加一个任务到队列中了,十分方便。
NSOperation 有一个非常实用的功能,那就是添加依赖。比如有 3 个任务:A: 从服务器上下载一张图片,B:给这张图片加个水印,C:把图片返回给服务器。这时就可以用到依赖了:
Swift
//1.任务一:下载图片
let operation1 = NSBlockOperation { () -> Void in
NSLog("下载图片 - %@", NSThread.currentThread())
NSThread.sleepForTimeInterval(1.0)
}
//2.任务二:打水印
let operation2 = NSBlockOperation { () -> Void in
NSLog("打水印 - %@", NSThread.currentThread())
NSThread.sleepForTimeInterval(1.0)
}
//3.任务三:上传图片
let operation3 = NSBlockOperation { () -> Void in
NSLog("上传图片 - %@", NSThread.currentThread())
NSThread.sleepForTimeInterval(1.0)
}
//4.设置依赖
operation2.addDependency(operation1) //任务二依赖任务一
operation3.addDependency(operation2) //任务三依赖任务二
//5.创建队列并加入任务
let queue = NSOperationQueue()
queue.addOperations([operation3, operation2, operation1], waitUntilFinished: false)
如何返回主线程
- 在GCD里我们知道
dispatch_async(dispatch_get_main_queue(), { () -> Void in
})
就能返回主线程执行UI操作.
- 在NSOperationQueue里我们是这样的:
NSOperationQueue.mainQueue().addOperationWithBlock { () -> Void in
}
就能返回主线程执行UI操作.
其他的一些属性
- NSOperation
BOOL executing; //判断任务是否正在执行
BOOL finished; //判断任务是否完成
void (^completionBlock)(void); //用来设置完成后需要执行的操作
(void)cancel; //取消任务
(void)waitUntilFinished; //阻塞当前线程直到此任务执行完毕
- NSOperationQueue
NSUInteger operationCount; //获取队列的任务数
(void)cancelAllOperations; //取消队列中所有的任务
(void)waitUntilAllOperationsAreFinished; //阻塞当前线程直到此队列中的所有任务执行完毕
[queue setSuspended:YES]; // 暂停queue
[queue setSuspended:NO]; // 继续queue
参考资料: