swift4.0版
1. NSOperation、NSOperationQueue 簡(jiǎn)介
NSOperation、NSOperationQueue 是蘋果提供給我們的一套多線程解決方案。實(shí)際上 NSOperation、NSOperationQueue 是基于 GCD 更高一層的封裝,完全面向?qū)ο蟆?/p>
GCD vs NSOperation Queue

我們可以看到,NSOperation Queue 作為高級(jí) API,有很多 GCD 沒有的功能,如需要支持:控制并發(fā)數(shù)、取消、添加依賴關(guān)系等需要使用 NSOperation Queue。
另外,由于 block 可復(fù)用性沒有 NSOperation 好,對(duì)于獨(dú)立性強(qiáng)、可復(fù)用性高的任務(wù)建議使用 NSOperation 實(shí)現(xiàn)。
當(dāng)然,NSOperation 在使用時(shí)需要 sub-classing,工作量較大,對(duì)于簡(jiǎn)單的任務(wù)使用 GCD 即可
NSThread就更少用了,對(duì)于實(shí)時(shí)性要求很高的任務(wù)則可以使用 NSThread
2. NSOperation、NSOperationQueue 操作和操作隊(duì)列
既然是基于 GCD 的更高一層的封裝。那么,GCD 中的一些概念同樣適用于 NSOperation、NSOperationQueue。在 NSOperation、NSOperationQueue 中也有類似的任務(wù)(操作)和 隊(duì)列(操作隊(duì)列) 的概念。
- 操作(Operation)
在 GCD 中是放在 block 中的。在 NSOperation 中,我們使用 NSOperation 子類 NSInvocationOperation、NSBlockOperation,或者自定義子類來(lái)封裝操作。 - 操作隊(duì)列(Operation Queues)
1.用來(lái)存放操作的隊(duì)列。不同于 GCD 中的調(diào)度隊(duì)列 FIFO(先進(jìn)先出)的原則。NSOperationQueue 對(duì)于添加到隊(duì)列中的操作,首先進(jìn)入準(zhǔn)備就緒的狀態(tài)(就緒狀態(tài)取決于操作之間的依賴關(guān)系),然后進(jìn)入就緒狀態(tài)的操作的開始執(zhí)行順序(非結(jié)束執(zhí)行順序)由操作之間相對(duì)的優(yōu)先級(jí)決定(優(yōu)先級(jí)是操作對(duì)象自身的屬性)。
2.操作隊(duì)列通過設(shè)置 最大并發(fā)操作數(shù)(maxConcurrentOperationCount) 來(lái)控制并發(fā)、串行
3.NSOperationQueue 為我們提供了兩種不同類型的隊(duì)列:主隊(duì)列和自定義隊(duì)列。主隊(duì)列運(yùn)行在主線程之上,而自定義隊(duì)列在后臺(tái)執(zhí)行
3. NSOperation、NSOperationQueue 使用步驟
NSOperation 需要配合 NSOperationQueue 來(lái)實(shí)現(xiàn)多線程。因?yàn)槟J(rèn)情況下,NSOperation 單獨(dú)使用時(shí)系統(tǒng)同步執(zhí)行操作,配合 NSOperationQueue 我們能更好的實(shí)現(xiàn)異步執(zhí)行。
NSOperation 實(shí)現(xiàn)多線程的使用步驟分為三步:
- 1.創(chuàng)建操作:先將需要執(zhí)行的操作封裝到一個(gè) NSOperation 對(duì)象中。
- 2.創(chuàng)建隊(duì)列:創(chuàng)建 NSOperationQueue 對(duì)象。
- 3.將操作加入到隊(duì)列中:將 NSOperation 對(duì)象添加到 NSOperationQueue 對(duì)象中。
之后呢,系統(tǒng)就會(huì)自動(dòng)將 NSOperationQueue 中的 NSOperation 取出來(lái),在新線程中執(zhí)行操作。
4.NSOperation 和 NSOperationQueue 基本使用
4.1 創(chuàng)建操作
NSOperation 是個(gè)抽象類,不能用來(lái)封裝操作。我們只有使用它的子類來(lái)封裝操作。我們有三種方式來(lái)封裝操作:
- 1.使用子類 NSInvocationOperation(swift已廢棄)
- 2.使用子類 NSBlockOperation
- 3.自定義繼承自 NSOperation 的子類,通過實(shí)現(xiàn)內(nèi)部相應(yīng)的方法來(lái)封裝操作
在不使用 NSOperationQueue,單獨(dú)使用 NSOperation 的情況下系統(tǒng)同步執(zhí)行操作:
4.1.1 使用子類 NSBlockOperation
/**
* 使用子類 BlockOperation
*/
func useBlockOperation(){
let op = BlockOperation.init {
for _ in 0...2{
sleep(2)
print("1---\(Thread.current)")
}
}
op.start()
print("main---\(Thread.current)")
}
輸出:
1---<NSThread: 0x2829bf040>{number = 1, name = main}
1---<NSThread: 0x2829bf040>{number = 1, name = main}
1---<NSThread: 0x2829bf040>{number = 1, name = main}
main---<NSThread: 0x2829bf040>{number = 1, name = main}
可以看到:在沒有使用 NSOperationQueue、在主線程中單獨(dú)使用 NSBlockOperation 執(zhí)行一個(gè)操作的情況下,操作是在當(dāng)前線程執(zhí)行的,并沒有開啟新線程。
NSBlockOperation 還提供了一個(gè)方法addExecutionBlock:,通過addExecutionBlock:就可以為 NSBlockOperation 添加額外的操作。這些操作(包括 init 中的操作)可以在不同的線程中同時(shí)(并發(fā))執(zhí)行。只有當(dāng)所有相關(guān)的操作已經(jīng)完成執(zhí)行時(shí),才視為完成。
/**
* 使用子類 BlockOperation
* 調(diào)用方法 AddExecutionBlock:
*/
@objc func useBlockOperationAddExecutionBlock(){
// 1.創(chuàng)建 BlockOperation 對(duì)象
let op = BlockOperation.init {
for _ in 0...2{
Thread.sleep(forTimeInterval: 2)
print("1---\(Thread.current)")
}
}
// 2.添加額外的操作
op.addExecutionBlock {
for _ in 0...2{
Thread.sleep(forTimeInterval: 2)
print("2---\(Thread.current)")
}
}
op.addExecutionBlock {
for _ in 0...2{
Thread.sleep(forTimeInterval: 2)
print("3---\(Thread.current)")
}
}
op.addExecutionBlock {
for _ in 0...2{
Thread.sleep(forTimeInterval: 2)
print("4---\(Thread.current)")
}
}
op.start()
print("current---\(Thread.current)")
}
輸出:
2---<NSThread: 0x2816c5fc0>{number = 4, name = (null)}
1---<NSThread: 0x28168ee00>{number = 1, name = main}
1---<NSThread: 0x28168ee00>{number = 1, name = main}
2---<NSThread: 0x2816c5fc0>{number = 4, name = (null)}
1---<NSThread: 0x28168ee00>{number = 1, name = main}
2---<NSThread: 0x2816c5fc0>{number = 4, name = (null)}
4---<NSThread: 0x2816c5fc0>{number = 4, name = (null)}
3---<NSThread: 0x28168ee00>{number = 1, name = main}
4---<NSThread: 0x2816c5fc0>{number = 4, name = (null)}
3---<NSThread: 0x28168ee00>{number = 1, name = main}
3---<NSThread: 0x28168ee00>{number = 1, name = main}
4---<NSThread: 0x2816c5fc0>{number = 4, name = (null)}
current---<NSThread: 0x28168ee00>{number = 1, name = main}
如果一個(gè) blockOperation 封裝了多個(gè)操作,在主線程執(zhí)行,系統(tǒng)只分配多一個(gè)線程與主線程來(lái)共同并發(fā),且阻塞當(dāng)前線程。這里與 objective-c 有差別,objective-c 是添加的操作的個(gè)數(shù)多,就會(huì)自動(dòng)開啟新線程。
如果是在其他線程上執(zhí)行看下輸出:
Thread.detachNewThreadSelector(#selector(useBlockOperationAddExecutionBlock), toTarget: self, with: nil)
輸出:
2---<NSThread: 0x2809f8d40>{number = 6, name = (null)}
1---<NSThread: 0x2809c1440>{number = 5, name = (null)}
2---<NSThread: 0x2809f8d40>{number = 6, name = (null)}
1---<NSThread: 0x2809c1440>{number = 5, name = (null)}
2---<NSThread: 0x2809f8d40>{number = 6, name = (null)}
1---<NSThread: 0x2809c1440>{number = 5, name = (null)}
3---<NSThread: 0x2809f8d40>{number = 6, name = (null)}
4---<NSThread: 0x2809c1440>{number = 5, name = (null)}
3---<NSThread: 0x2809f8d40>{number = 6, name = (null)}
4---<NSThread: 0x2809c1440>{number = 5, name = (null)}
3---<NSThread: 0x2809f8d40>{number = 6, name = (null)}
4---<NSThread: 0x2809c1440>{number = 5, name = (null)}
current---<NSThread: 0x2809c1440>{number = 5, name = (null)}
如果一個(gè) blockOperation 封裝了多個(gè)操作,在其他線程執(zhí)行,系統(tǒng)只分配兩條線程并發(fā)。
4.1.2 使用自定義繼承自 NSOperation 的子類
如果使用子類 NSInvocationOperation、NSBlockOperation 不能滿足日常需求,我們可以使用自定義繼承自 NSOperation 的子類??梢酝ㄟ^重寫 main 或者 start 方法 來(lái)定義自己的 NSOperation 對(duì)象。重寫main方法比較簡(jiǎn)單,我們不需要管理操作的狀態(tài)屬性isExecuting 和 isFinished等。當(dāng)main執(zhí)行完返回的時(shí)候,這個(gè)操作就結(jié)束了。
先定義一個(gè)繼承自 NSOperation 的子類,重寫main方法。
//
// CustomOperation.swift
//
import UIKit
class CustomOperation: Operation {
override func main() {
if self.isCancelled == false{
for _ in 0...2 {
Thread.sleep(forTimeInterval: 2)
print("1---\(Thread.current)")
}
}
}
}
然后使用:
/**
* 使用自定義繼承自 Operation 的子類
*/
func useCustomOperation(){
let op = CustomOperation.init()
op.start()
}
輸出:
1---<NSThread: 0x2835e9f80>{number = 1, name = main}
1---<NSThread: 0x2835e9f80>{number = 1, name = main}
1---<NSThread: 0x2835e9f80>{number = 1, name = main}
另外一種方式是重寫NSOperation的start方法,這種方法就需要你自己去控制NSOperation的完成,取消,執(zhí)行等。通過官方文檔可以知道,實(shí)現(xiàn)并行的自定義子類,需要重寫以下幾種方法或?qū)傩裕?/p>
start:把需要執(zhí)行的任務(wù)放在start方法里,并且要檢查任務(wù)是否已取消,更新任務(wù)的狀態(tài)等。(注意:任何時(shí)候都不能調(diào)用父類的start方法)asynchronous:表示是否并發(fā)執(zhí)行executing:表示任務(wù)是否正在執(zhí)行,需要手動(dòng)調(diào)用KVO方法來(lái)進(jìn)行通知,方便其他類監(jiān)聽了任務(wù)的該屬性finished:表示任務(wù)是否結(jié)束,需要手動(dòng)調(diào)用KVO方法來(lái)進(jìn)行通知,變量也需要監(jiān)聽改屬性的值,用于判斷任務(wù)是否結(jié)束
class CustomOperation: Operation {
var _finished:Bool = false
var _executing:Bool = false
override var isExecuting: Bool{
set{
self.willChangeValue(forKey: "isExecuting")
_executing = newValue
self.didChangeValue(forKey: "isExecuting")
}
get{
return _executing
}
}
override var isFinished: Bool{
set{
self.willChangeValue(forKey: "isFinished")
_finished = newValue
self.didChangeValue(forKey: "isFinished")
}
get{
return _finished
}
}
override var isConcurrent: Bool{
get{ return true }
}
override func start() {
synchronized(self) {//線程安全
if self.isCancelled{
self.isFinished = true
return
}
self.isExecuting = true//設(shè)置執(zhí)行狀態(tài)并調(diào)用KVO通知
Thread.sleep(forTimeInterval: 2)//耗時(shí)操作:圖片下載等.
print("耗時(shí)操作: --- \(Thread.current)")
self.isFinished = true//設(shè)置完成狀態(tài)并調(diào)用KVO通知
}
}
synchronized(_ lock: AnyObject, _ action: () -> Void){
objc_sync_enter(lock)
defer { objc_sync_exit(lock) }
action()
}
}
func useCustomOperation(){
let op1 = CustomOperation.init()
let op2 = CustomOperation.init()
let queue = OperationQueue.init()
queue.addOperation(op1)
queue.addOperation(op2)
}
輸出:
耗時(shí)操作: --- <NSThread: 0x283fe9c80>{number = 4, name = (null)}
耗時(shí)操作: --- <NSThread: 0x283fc1f80>{number = 5, name = (null)}
關(guān)于自定義
NSOperation,可以閱讀SDWebImage 的 SDWebImageDownloaderOperation 和 SDWebImageDownloader源碼來(lái)學(xué)習(xí)更多知識(shí)
4.1 創(chuàng)建隊(duì)列
NSOperationQueue 一共有兩種隊(duì)列:主隊(duì)列、自定義隊(duì)列
-
主隊(duì)列
凡是添加到主隊(duì)列中的操作,都會(huì)放到主線程中執(zhí)行// 主隊(duì)列獲取方法 let queue = OperationQueue.main -
自定義隊(duì)列
添加到這種隊(duì)列中的操作,就會(huì)自動(dòng)放到子線程中執(zhí)行
同時(shí)包含了串行、并發(fā)功能// 自定義隊(duì)列創(chuàng)建方法 let queue = OperationQueue.init()
4.3 將操作加入到隊(duì)列中
NSOperation 配合 NSOperationQueue 來(lái)實(shí)現(xiàn)多線程
那么我們需要將創(chuàng)建好的操作加入到隊(duì)列中去。總共有兩種方法:
1.open func addOperation(_ op: Operation)需要先創(chuàng)建操作,再將創(chuàng)建好的操作加入到創(chuàng)建好的隊(duì)列中去
/**
* 使用 addOperation: 將操作加入到操作隊(duì)列中
*/
func addOperationToQueue(){
// 自定義隊(duì)列創(chuàng)建方法
let queue = OperationQueue.init()
// 1.創(chuàng)建 BlockOperation 對(duì)象
let op = BlockOperation.init {
for _ in 0...2{
Thread.sleep(forTimeInterval: 1)
print("1---\(Thread.current)")
}
}
let op1 = BlockOperation.init {
for _ in 0...2{
Thread.sleep(forTimeInterval: 1)
print("1---\(Thread.current)")
}
}
let op2 = BlockOperation.init {
for _ in 0...2{
Thread.sleep(forTimeInterval: 1)
print("1---\(Thread.current)")
}
}
queue.addOperation(op)
queue.addOperation(op1)
queue.addOperation(op2)
print("current---\(Thread.current)")
}
輸出:
current---<NSThread: 0x282da0300>{number = 1, name = main}
1---<NSThread: 0x282df6240>{number = 5, name = (null)}
1---<NSThread: 0x282de4380>{number = 3, name = (null)}
1---<NSThread: 0x282df2580>{number = 6, name = (null)}
1---<NSThread: 0x282df6240>{number = 5, name = (null)}
1---<NSThread: 0x282df2580>{number = 6, name = (null)}
1---<NSThread: 0x282de4380>{number = 3, name = (null)}
1---<NSThread: 0x282df6240>{number = 5, name = (null)}
1---<NSThread: 0x282df2580>{number = 6, name = (null)}
1---<NSThread: 0x282de4380>{number = 3, name = (null)}
使用 NSOperation 子類創(chuàng)建操作,并使用 addOperation 將操作加入到操作隊(duì)列后能夠開啟新線程,進(jìn)行并發(fā)執(zhí)行。
2.open func addOperation(_ block: @escaping () -> Void)無(wú)需先創(chuàng)建操作,在 block 中添加操作,直接將包含操作的 block 加入到隊(duì)列中
/**
* 使用 addOperationWithBlock: 將操作加入到操作隊(duì)列中
*/
func addOperationWithBlockToQueue(){
// 自定義隊(duì)列創(chuàng)建方法
let queue = OperationQueue.init()
queue.addOperation {
for _ in 0...2{
Thread.sleep(forTimeInterval: 1)
print("1---\(Thread.current)")
}
}
queue.addOperation {
for _ in 0...2{
Thread.sleep(forTimeInterval: 1)
print("2---\(Thread.current)")
}
}
queue.addOperation {
for _ in 0...2{
Thread.sleep(forTimeInterval: 1)
print("3---\(Thread.current)")
}
}
print("current---\(Thread.current)")
}
輸出:
current---<NSThread: 0x282832e00>{number = 1, name = main}
1---<NSThread: 0x282864580>{number = 4, name = (null)}
2---<NSThread: 0x282854a80>{number = 6, name = (null)}
3---<NSThread: 0x282854880>{number = 5, name = (null)}
3---<NSThread: 0x282854880>{number = 5, name = (null)}
2---<NSThread: 0x282854a80>{number = 6, name = (null)}
1---<NSThread: 0x282864580>{number = 4, name = (null)}
2---<NSThread: 0x282854a80>{number = 6, name = (null)}
3---<NSThread: 0x282854880>{number = 5, name = (null)}
1---<NSThread: 0x282864580>{number = 4, name = (null)}
使用 addOperation 將操作加入到操作隊(duì)列后能夠開啟新線程,進(jìn)行并發(fā)執(zhí)行
5.NSOperationQueue 控制串行執(zhí)行、并發(fā)執(zhí)行
NSOperationQueue是通過maxConcurrentOperationCount屬性來(lái)控制串行執(zhí)行、并發(fā)執(zhí)行的
maxConcurrentOperationCount:叫做最大并發(fā)操作數(shù)。用來(lái)控制一個(gè)特定隊(duì)列中可以有多少個(gè)操作同時(shí)參與并發(fā)執(zhí)行。
-
maxConcurrentOperationCount默認(rèn)情況下為-1,表示不進(jìn)行限制,可進(jìn)行并發(fā)執(zhí)行 -
maxConcurrentOperationCount為1時(shí),隊(duì)列為串行隊(duì)列。只能串行執(zhí)行。 -
maxConcurrentOperationCoun大于1時(shí),隊(duì)列為并發(fā)隊(duì)列。操作并發(fā)執(zhí)行,當(dāng)然這個(gè)值不應(yīng)超過系統(tǒng)限制,即使自己設(shè)置一個(gè)很大的值,系統(tǒng)也會(huì)自動(dòng)調(diào)整為 min{自己設(shè)定的值,系統(tǒng)設(shè)定的默認(rèn)最大值}。
注意:這里 maxConcurrentOperationCount 控制的不是并發(fā)線程的數(shù)量,而是一個(gè)隊(duì)列中同時(shí)能并發(fā)執(zhí)行的最大操作數(shù)。而且一個(gè)操作也并非只能在一個(gè)線程中運(yùn)行。
/**
* 設(shè)置 MaxConcurrentOperationCount(最大并發(fā)操作數(shù))
*/
func setMaxConcurrentOperationCount(){
// 自定義隊(duì)列創(chuàng)建方法
let queue = OperationQueue.init()
queue.maxConcurrentOperationCount = 1//串行隊(duì)列
// queue.maxConcurrentOperationCount = 2//并發(fā)隊(duì)列
queue.addOperation {
for _ in 0...2{
Thread.sleep(forTimeInterval: 1)
print("1---\(Thread.current)")
}
}
queue.addOperation {
for _ in 0...2{
Thread.sleep(forTimeInterval: 1)
print("2---\(Thread.current)")
}
}
queue.addOperation {
for _ in 0...2{
Thread.sleep(forTimeInterval: 1)
print("3---\(Thread.current)")
}
}
print("current---\(Thread.current)")
}
最大并發(fā)操作數(shù)為1 輸出結(jié)果:
current---<NSThread: 0x2803a5fc0>{number = 1, name = main}
1---<NSThread: 0x2803f0a80>{number = 4, name = (null)}
1---<NSThread: 0x2803f0a80>{number = 4, name = (null)}
1---<NSThread: 0x2803f0a80>{number = 4, name = (null)}
2---<NSThread: 0x2803c4e40>{number = 5, name = (null)}
2---<NSThread: 0x2803c4e40>{number = 5, name = (null)}
2---<NSThread: 0x2803c4e40>{number = 5, name = (null)}
3---<NSThread: 0x2803c4e40>{number = 5, name = (null)}
3---<NSThread: 0x2803c4e40>{number = 5, name = (null)}
3---<NSThread: 0x2803c4e40>{number = 5, name = (null)}
最大并發(fā)操作數(shù)為2 輸出結(jié)果:
current---<NSThread: 0x2837f1f80>{number = 1, name = main}
2---<NSThread: 0x28379ccc0>{number = 5, name = (null)}
1---<NSThread: 0x2837b2180>{number = 4, name = (null)}
1---<NSThread: 0x2837b2180>{number = 4, name = (null)}
2---<NSThread: 0x28379ccc0>{number = 5, name = (null)}
2---<NSThread: 0x28379ccc0>{number = 5, name = (null)}
1---<NSThread: 0x2837b2180>{number = 4, name = (null)}
3---<NSThread: 0x2837b2180>{number = 4, name = (null)}
3---<NSThread: 0x2837b2180>{number = 4, name = (null)}
3---<NSThread: 0x2837b2180>{number = 4, name = (null)}
從輸出可以看出,當(dāng)
maxConcurrentOperationCount為1時(shí),操作是按順序串行執(zhí)行的,操作執(zhí)行完一個(gè)才執(zhí)行下一個(gè)。當(dāng)maxConcurrentOperationCount大于1時(shí),操作是并發(fā)執(zhí)行的,同時(shí)有兩個(gè)操作執(zhí)行。而開啟線程的數(shù)量是同系統(tǒng)決定的,不需要自己管理
6.NSOperation 操作依賴
NSOperationQueue可以通過addDependency方法來(lái)控制操作的執(zhí)行順序。關(guān)于依賴相關(guān)屬性及方法有三個(gè):
open func addDependency(_ op: Operation)// 添加依賴,使當(dāng)前操作依賴于操作 op 的完成
open func removeDependency(_ op: Operation)//移除依賴,取消當(dāng)前操作對(duì)操作 op 的依賴
open var dependencies: [Operation] { get }//在當(dāng)前操作開始執(zhí)行之前完成執(zhí)行的所有操作對(duì)象數(shù)組
/**
* 操作依賴
* 使用方法:addDependency:
*/
func addDependency(){
// 1.創(chuàng)建隊(duì)列
let queue = OperationQueue.init()
// 2.創(chuàng)建操作
let op = BlockOperation.init {
for _ in 0...2{
print("0---\(Thread.current)")
}
}
let op1 = BlockOperation.init {
for _ in 0...2{
Thread.sleep(forTimeInterval: 1)
print("1---\(Thread.current)")
}
}
// 3.添加依賴
op.addDependency(op1)// 讓op 依賴于 op1,則先執(zhí)行op1,在執(zhí)行op,右先左后
// 4.添加操作到隊(duì)列中
queue.addOperation(op)
queue.addOperation(op1)
print("current---\(Thread.current)")
}
輸出:
current---<NSThread: 0x280e0df40>{number = 1, name = main}
1---<NSThread: 0x280e64e00>{number = 5, name = (null)}
1---<NSThread: 0x280e64e00>{number = 5, name = (null)}
1---<NSThread: 0x280e64e00>{number = 5, name = (null)}
0---<NSThread: 0x280e64a40>{number = 6, name = (null)}
0---<NSThread: 0x280e64a40>{number = 6, name = (null)}
0---<NSThread: 0x280e64a40>{number = 6, name = (null)}
7.NSOperation 優(yōu)先級(jí)
NSOperation 提供了queuePriority(優(yōu)先級(jí))屬性
public enum QueuePriority : Int {
case veryLow
case low
case normal
case high
case veryHigh
}
/**
* 優(yōu)先級(jí)使用
* 使用屬性:queuePriority
*/
func queuePriority(){
// 1.創(chuàng)建隊(duì)列
let queue = OperationQueue.init()
// 2.創(chuàng)建操作
let op = BlockOperation.init {
print("veryLow---\(Thread.current)")
}
let op1 = BlockOperation.init {
print("low---\(Thread.current)")
}
let op2 = BlockOperation.init {
print("normal---\(Thread.current)")
}
let op3 = BlockOperation.init {
print("high---\(Thread.current)")
}
let op4 = BlockOperation.init {
print("veryHigh---\(Thread.current)")
}
let op5 = BlockOperation.init {
print("normal5---\(Thread.current)")
}
op.queuePriority = .veryLow
op1.queuePriority = .low
op2.queuePriority = .normal
op5.queuePriority = .normal
op3.queuePriority = .high
op4.queuePriority = .veryHigh
queue.addOperation(op)
queue.addOperation(op1)
queue.addOperation(op2)
queue.addOperation(op3)
queue.addOperation(op4)
queue.addOperation(op5)
}
輸出:
veryLow---<NSThread: 0x280d20640>{number = 4, name = (null)}
low---<NSThread: 0x280d2cb80>{number = 5, name = (null)}
normal---<NSThread: 0x280d37d00>{number = 3, name = (null)}
high---<NSThread: 0x280d37d00>{number = 3, name = (null)}
normal5---<NSThread: 0x280d37d00>{number = 3, name = (null)}
veryHigh---<NSThread: 0x280d20640>{number = 4, name = (null)}
這里可能會(huì)有個(gè)誤區(qū),認(rèn)為優(yōu)先級(jí)就可以決定操作的執(zhí)行順序。以上輸出可以看到并不能,這些優(yōu)先級(jí)都是相對(duì)的,并不是說(shuō)必須按高到低的優(yōu)先級(jí)來(lái)執(zhí)行。這里面并沒有一個(gè)特別嚴(yán)格順序。只是在分配資源上有傾向性。如果隊(duì)列需要有嚴(yán)格的執(zhí)行順序,還是要添加依賴關(guān)系的
8. NSOperation、NSOperationQueue 線程間的通信
/**
* 線程間通信
*/
func communication(){
// 1.創(chuàng)建隊(duì)列
let queue = OperationQueue.init()
queue.addOperation {
sleep(1) //耗時(shí)操作 如:下載文件
print("1---\(Thread.current)")
OperationQueue.main.addOperation {
print("更新操作---\(Thread.current)")
}
}
}
輸出:
1---<NSThread: 0x281bdc9c0>{number = 5, name = (null)}
更新操作---<NSThread: 0x281bb1f80>{number = 1, name = main}
9. NSOperation、NSOperationQueue 線程同步和線程安全
- 線程安全:
若每個(gè)線程中對(duì)全局變量、靜態(tài)變量只有讀操作,而無(wú)寫操作,一般來(lái)說(shuō),這個(gè)全局變量是線程安全的;若有多個(gè)線程同時(shí)執(zhí)行寫操作(更改變量),一般都需要考慮線程同步,否則的話就可能影響線程安全 - 線程同步:
可理解為線程 A 和 線程 B 一塊配合,A 執(zhí)行到一定程度時(shí)要依靠線程 B 的某個(gè)結(jié)果,于是停下來(lái),示意 B 運(yùn)行;B 依言執(zhí)行,再將結(jié)果給 A;A 再繼續(xù)操作
下面模擬一下火車票的售賣方式:
/**
* 非線程安全
* 初始化火車票數(shù)量、賣票窗口、并開始賣票
*/
func initTicketStatusNoSave(){
print("mainThread---\(Thread.current)")
let queue = OperationQueue.init()
queue.addOperation {
self.saleTickeSafe()//開始賣票
}
queue.addOperation {
self.saleTickeSafe()//開始賣票
}
}
/**
* 售賣火車票(非線程安全)
*/
func saleTickeNoSafe(){
while true {
if self.num > 0{
self.num = self.num - 1
print("剩余票數(shù):\(self.num), 窗口:\(Thread.current)")
Thread.sleep(forTimeInterval: 0.2)
}else{
print("所有火車票已賣完")
break
}
}
}
輸出:
mainThread---<NSThread: 0x282032e00>{number = 1, name = main}
剩余票數(shù):49, 窗口:<NSThread: 0x2820738c0>{number = 3, name = (null)}
剩余票數(shù):48, 窗口:<NSThread: 0x28206afc0>{number = 6, name = (null)}
剩余票數(shù):47, 窗口:<NSThread: 0x28206afc0>{number = 6, name = (null)}
剩余票數(shù):46, 窗口:<NSThread: 0x2820738c0>{number = 3, name = (null)}
剩余票數(shù):44, 窗口:<NSThread: 0x2820738c0>{number = 3, name = (null)}
剩余票數(shù):44, 窗口:<NSThread: 0x28206afc0>{number = 6, name = (null)}
剩余票數(shù):43, 窗口:<NSThread: 0x2820738c0>{number = 3, name = (null)}
剩余票數(shù):43, 窗口:<NSThread: 0x28206afc0>{number = 6, name = (null)}
剩余票數(shù):42, 窗口:<NSThread: 0x2820738c0>{number = 3, name = (null)}
......中間省略一部分
所有火車票已賣完
剩余票數(shù):0, 窗口:<NSThread: 0x28206afc0>{number = 6, name = (null)}
所有火車票已賣完
輸出可以看數(shù)據(jù)是錯(cuò)亂的,這是非線程安全
線程安全解決方案:可以給線程加鎖,在一個(gè)線程執(zhí)行該操作的時(shí)候,不允許其他線程進(jìn)行操作。iOS 實(shí)現(xiàn)線程加鎖有很多種方式。@synchronized、 NSLock、NSRecursiveLock、NSCondition、NSConditionLock、pthread_mutex、dispatch_semaphore、OSSpinLock、atomic(property) set/ge等等各種方式。iOS 線程同步方案學(xué)習(xí)總結(jié),這里我們使用 NSLock 對(duì)象來(lái)解決線程同步問題。
/**
* 使用 NSLock 保證線程安全
* 初始化火車票數(shù)量、賣票窗口、并開始賣票
*/
func initTicketStatusSave(){
print("mainThread---\(Thread.current)")
let queue = OperationQueue.init()
queue.addOperation {
self.saleTickeSafe()//開始賣票
}
queue.addOperation {
self.saleTickeSafe()//開始賣票
}
}
/**
* 售賣火車票
*/
func saleTickeSafe(){
while true {
lock.lock()
if self.num > 0{
self.num = self.num - 1
print("剩余票數(shù):\(self.num), 窗口:\(Thread.current)")
Thread.sleep(forTimeInterval: 0.2)
}else{
print("所有火車票已賣完")
break
}
lock.unlock()
}
}
輸出:
mainThread---<NSThread: 0x282f99f80>{number = 1, name = main}
剩余票數(shù):49, 窗口:<NSThread: 0x282fda580>{number = 4, name = (null)}
剩余票數(shù):48, 窗口:<NSThread: 0x282fda580>{number = 4, name = (null)}
剩余票數(shù):47, 窗口:<NSThread: 0x282fda580>{number = 4, name = (null)}
剩余票數(shù):46, 窗口:<NSThread: 0x282fda580>{number = 4, name = (null)}
剩余票數(shù):45, 窗口:<NSThread: 0x282fda580>{number = 4, name = (null)}
剩余票數(shù):44, 窗口:<NSThread: 0x282fda580>{number = 4, name = (null)}
剩余票數(shù):43, 窗口:<NSThread: 0x282fda580>{number = 4, name = (null)}
......中間省略一部分
剩余票數(shù):1, 窗口:<NSThread: 0x282fd3a00>{number = 3, name = (null)}
剩余票數(shù):0, 窗口:<NSThread: 0x282fd3a00>{number = 3, name = (null)}
所有火車票已賣完
輸出可以看出,在保證線程安全的情況下,數(shù)據(jù)沒有錯(cuò)亂。
10. NSOperation、NSOperationQueue 常用屬性和方法歸納
10.1 NSOperation 常用屬性和方法
- 取消操作方法:
-
(void)cancel;可取消操作,實(shí)質(zhì)是標(biāo)記 isCancelled 狀態(tài)。
-
- 判斷操作狀態(tài)方法:
-
(BOOL)isFinished;判斷操作是否已經(jīng)結(jié)束。 -
(BOOL)isCancelled;判斷操作是否已經(jīng)標(biāo)記為取消。 -
(BOOL)isExecuting;判斷操作是否正在在運(yùn)行。 -
(BOOL)isReady;判斷操作是否處于準(zhǔn)備就緒狀態(tài),這個(gè)值和操作的依賴關(guān)系相關(guān)。
-
- 操作同步:
-
(void)waitUntilFinished;阻塞當(dāng)前線程,直到該操作結(jié)束??捎糜诰€程執(zhí)行順序的同步。 -
(void)setCompletionBlock:(void (^)(void))block;completionBlock 會(huì)在當(dāng)前操作執(zhí)行完畢時(shí)執(zhí)行 completionBlock。 -
(void)addDependency:(NSOperation *)op;添加依賴,使當(dāng)前操作依賴于操作 op 的完成。 -
(void)removeDependency:(NSOperation *)op;移除依賴,取消當(dāng)前操作對(duì)操作 op 的依賴。 -
@property (readonly, copy) NSArray<NSOperation *> *dependencies;在當(dāng)前操作開始執(zhí)行之前完成執(zhí)行的所有操作對(duì)象數(shù)組。
-
10.2 NSOperationQueue 常用屬性和方法
- 取消/暫停/恢復(fù)操作:
-
(void)cancelAllOperations;可以取消隊(duì)列的所有操作。 -
(BOOL)isSuspended;判斷隊(duì)列是否處于暫停狀態(tài)。 YES 為暫停狀態(tài),NO 為恢復(fù)狀態(tài)。 -
(void)setSuspended:(BOOL)b;可設(shè)置操作的暫停和恢復(fù),YES 代表暫停隊(duì)列,NO 代表恢復(fù)隊(duì)列。
-
- 操作同步:
-
(void)waitUntilAllOperationsAreFinished;阻塞當(dāng)前線程,直到隊(duì)列中的操作全部執(zhí)行完畢。
-
- 添加/獲取操作:
-
(void)addOperationWithBlock:(void (^)(void))block;向隊(duì)列中添加一個(gè) NSBlockOperation 類型操作對(duì)象。 -
(void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait;向隊(duì)列中添加操作數(shù)組,wait 標(biāo)志是否阻塞當(dāng)前線程直到所有操作結(jié)束 -
(NSArray *)operations;當(dāng)前在隊(duì)列中的操作數(shù)組(某個(gè)操作執(zhí)行結(jié)束后會(huì)自動(dòng)從這個(gè)數(shù)組清除)。 -
(NSUInteger)operationCount;當(dāng)前隊(duì)列中的操作數(shù)。
-
- 獲取隊(duì)列:
-
(id)currentQueue;獲取當(dāng)前隊(duì)列,如果當(dāng)前線程不是在 NSOperationQueue 上運(yùn)行則返回 nil。 -
(id)mainQueue;獲取主隊(duì)列。
-
注意:
1.這里的暫停和取消(包括操作的取消和隊(duì)列的取消)并不代表可以將當(dāng)前的操作立即取消,而是當(dāng)當(dāng)前的操作執(zhí)行完畢之后不再執(zhí)行新的操作。
2.暫停和取消的區(qū)別在于:暫停操作之后還可以恢復(fù)操作,繼續(xù)向下執(zhí)行;而取消操作之后,所有的操作就清空了,無(wú)法再接著執(zhí)行剩下的操作。
參考資料:
iOS多線程:『NSOperation、NSOperationQueue』詳盡總結(jié)
細(xì)說(shuō) NSoperation
相關(guān)簡(jiǎn)書:
iOS GCD學(xué)習(xí)總結(jié)(一)
iOS GCD學(xué)習(xí)總結(jié)(二)
iOS 線程同步方案學(xué)習(xí)總結(jié)