(上圖是一些 Operation 經常使用的功能的UML圖)swift
GCD 是在封裝了C,進行多線程開發。而 Operation & OperationQueue 則是封裝了 GCD 進行多線程開發。在平時用GCD就能解決不少多線程任務開發的問題, 那爲何還要使用 Operation & OperationQueue 呢?安全
使用 Operation & OperationQueue 可以:bash
Operation 一個操做,能夠當作一個任務。相似於 GCD 裏面的 DispatchItem。Operation 是一個抽象類,裏面定義了任務安全執行的邏輯。 在實際使用的時候通常使用系統建立的子類 BlockOperation (Objective-C 還可使用 NSInvocationOperation)。或者建立 Operation 的自定義子類。多線程
OperationQueue 操做的隊列,管理操做的執行,相似於 GCD 裏面的 DispatchQueue。在 GCD 中,任務的執行順序是先進先出 FIFO。OperationQueue 執行 Operation 的時候依據 Operation 的優先級和是否在準備就緒的狀態。當一個 Operation 加入 OperationQueue 之後,只有等到 Operation 執行完畢(任務取消也是執行完畢)才能從 OperationQueue 裏面移除。OperationQueue 不能直接把 Operation 移除。併發
增長一個輔助方法輸出當前內容和執行的線程app
func printOperation<T>(_ message: T) {
print(" \(message), thread = \(Thread.current)")
}
複製代碼
let operation = BlockOperation {
printOperation("Op init block task 1 begin")
Thread.sleep(forTimeInterval: 3)
printOperation("Op init block task 1 end")
}
operation.addExecutionBlock {
printOperation("Op task 2 begin")
Thread.sleep(forTimeInterval: 3)
printOperation("Op task 2 end")
}
operation.addExecutionBlock {
printOperation("Op task 3 begin")
Thread.sleep(forTimeInterval: 3)
printOperation("Op task 3 end")
}
複製代碼
初始化一個 BlockOperation, 隨後能夠加入多個任務異步
能夠定義本身的同步執行子類,和異步執行子類。同步執行子類的建立須要配置的比較少,通常狀況只須要建立一個本身的初始化方法和覆蓋 main() 方法就行了。異步執行的則比較複雜,須要本身設置多個狀態屬性。async
class CustomOperation: Operation {
private let address: String
init(address: String) {
self.address = address
}
override func main() {
if isCancelled {
return
}
printOperation("CustomOperation task 1 begin")
Thread.sleep(forTimeInterval: 3)
printOperation("CustomOperation task 1 end")
}
}
複製代碼
具體可參考:Apple Operation Documentationide
let operationTask2 = BlockOperation {
printOperation("Operation task 2 begin")
Thread.sleep(forTimeInterval: 3)
printOperation("Operation task 2 end")
}
let operationTask3 = BlockOperation {
printOperation("Operation task 3 begin")
Thread.sleep(forTimeInterval: 3)
printOperation("Operation task 3 end")
}
let operationTask4 = BlockOperation {
printOperation("Operation task 4 begin")
Thread.sleep(forTimeInterval: 3)
printOperation("Operation task 4 end")
}
// 2 -> 4 - > 3
operationTask3.addDependency(operationTask4)
operationTask4.addDependency(operationTask2)
複製代碼
在上面的例子中,operationTask4 的開始執行依賴 operationTask2,operationTask3 的開始執行依賴 operationTask4。ui
let operationTask2 = BlockOperation {
printOperation("Operation task 2 begin")
Thread.sleep(forTimeInterval: 3)
printOperation("Operation task 2 end")
}
let operationTask3 = BlockOperation {
printOperation("Operation task 3 begin")
Thread.sleep(forTimeInterval: 3)
printOperation("Operation task 3 end")
}
let operationTask4 = BlockOperation {
printOperation("Operation task 4 begin")
Thread.sleep(forTimeInterval: 3)
printOperation("Operation task 4 end")
}
operationTask2.queuePriority = .veryHigh
operationTask3.queuePriority = .normal
operationTask4.queuePriority = .low
複製代碼
在上面設置了 Operation 的隊列優先級屬性
operation.completionBlock = {
DispatchQueue.main.async {
// Task done
}
}
複製代碼
和 GCD 裏面的 DispatchWorkItem 同樣,任務能夠本身執行,也能夠提交給隊列執行。
本身執行調用 start() 方法。下面看一些例子:
BlockOperation 的單個任務
private func singleBlockOperation() {
printOperation("Test begin")
let operation = BlockOperation {
printOperation("Operation init block task 1 begin")
Thread.sleep(forTimeInterval: 3)
printOperation("Operation init block task 1 end")
}
printOperation("Operation start")
operation.start()
printOperation("Test end")
}
複製代碼
輸出:
Test begin, thread = <NSThread: 0x6000039ead80>{number = 1, name = main}
Operation start, thread = <NSThread: 0x6000039ead80>{number = 1, name = main}
Operation init block task 1 begin, thread = <NSThread: 0x6000039ead80>{number = 1, name = main}
Operation init block task 1 end, thread = <NSThread: 0x6000039ead80>{number = 1, name = main}
Test end, thread = <NSThread: 0x6000039ead80>{number = 1, name = main}
複製代碼
能夠看到 start() 默認是一個同步執行方法。它會等待任務在當前線程執行完畢 (例子此時在主線程)。
BlockOperation 的多個任務
狀況一:
private func singleBlockMutipleTaskOperation() {
printOperation("Test begin")
let operation = BlockOperation {
printOperation("Operation init block task 1 begin")
Thread.sleep(forTimeInterval: 3)
printOperation("Operation init block task 1 end")
}
operation.addExecutionBlock {
printOperation("Operation task 2 begin")
Thread.sleep(forTimeInterval: 3)
printOperation("Operation task 2 end")
}
operation.addExecutionBlock {
printOperation("Operation task 3 begin")
Thread.sleep(forTimeInterval: 3)
printOperation("Operation task 3 end")
}
printOperation("Operation start")
operation.start()
printOperation("Test end")
}
複製代碼
輸出:
Test begin, thread = <NSThread: 0x6000006b6d00>{number = 1, name = main}
Operation start, thread = <NSThread: 0x6000006b6d00>{number = 1, name = main}
Operation init block task 1 begin, thread = <NSThread: 0x6000006b6d00>{number = 1, name = main}
Operation task 2 begin, thread = <NSThread: 0x600000635d00>{number = 6, name = (null)}
Operation task 3 begin, thread = <NSThread: 0x600000620e80>{number = 7, name = (null)}
Operation task 3 end, thread = <NSThread: 0x600000620e80>{number = 7, name = (null)}
Operation init block task 1 end, thread = <NSThread: 0x6000006b6d00>{number = 1, name = main}
Operation task 2 end, thread = <NSThread: 0x600000635d00>{number = 6, name = (null)}
Test end, thread = <NSThread: 0x6000006b6d00>{number = 1, name = main}
複製代碼
此時 BlockOperation 第一個任務 BlockOperation { // task }
在當前線程(當前線程是主線程)執行,額外的任務 addExecutionBlock { // task }
在其它線程執行。
狀況二:
private func singleBlockMutipleTaskOperationAddTasks() {
printOperation("Test begin")
let operation = BlockOperation()
operation.addExecutionBlock {
printOperation("Operation task 2 begin")
Thread.sleep(forTimeInterval: 3)
printOperation("Operation task 2 end")
}
operation.addExecutionBlock {
printOperation("Operation task 3 begin")
Thread.sleep(forTimeInterval: 3)
printOperation("Operation task 3 end")
}
printOperation("Operation start")
operation.start()
printOperation("Test end")
}
複製代碼
輸出:
Test begin, thread = <NSThread: 0x6000009c95c0>{number = 1, name = main}
Operation start, thread = <NSThread: 0x6000009c95c0>{number = 1, name = main}
Operation task 2 begin, thread = <NSThread: 0x6000009c95c0>{number = 1, name = main}
Operation task 3 begin, thread = <NSThread: 0x600000959f40>{number = 6, name = (null)}
Operation task 3 end, thread = <NSThread: 0x600000959f40>{number = 6, name = (null)}
Operation task 2 end, thread = <NSThread: 0x6000009c95c0>{number = 1, name = main}
Test end, thread = <NSThread: 0x6000009c95c0>{number = 1, name = main}
複製代碼
和狀況一的結果相似: 第一個任務 addExecutionBlock { // task1 }
在當前線程(當前線程是主線程)執行,額外的任務 addExecutionBlock { // tasks }
在其它線程執行。
private func queueAddOperations() {
printOperation("Test begin")
let queue = OperationQueue()
queue.addOperation {
printOperation("Operation task 1 begin")
Thread.sleep(forTimeInterval: 3)
printOperation("Operation task 1 end")
}
let operationTask2 = BlockOperation {
printOperation("Operation task 2 begin")
Thread.sleep(forTimeInterval: 3)
printOperation("Operation task 2 end")
}
let operationTask3 = BlockOperation {
printOperation("Operation task 3 begin")
Thread.sleep(forTimeInterval: 3)
printOperation("Operation task 3 end")
}
queue.addOperation(operationTask2)
queue.addOperation(operationTask3)
printOperation("Test end")
}
複製代碼
輸出:
Test begin, thread = <NSThread: 0x600002bc6cc0>{number = 1, name = main}
Test end, thread = <NSThread: 0x600002bc6cc0>{number = 1, name = main}
Operation task 1 begin, thread = <NSThread: 0x600002b51980>{number = 7, name = (null)}
Operation task 2 begin, thread = <NSThread: 0x600002bb3400>{number = 8, name = (null)}
Operation task 3 begin, thread = <NSThread: 0x600002bc3280>{number = 9, name = (null)}
Operation task 2 end, thread = <NSThread: 0x600002bb3400>{number = 8, name = (null)}
Operation task 3 end, thread = <NSThread: 0x600002bc3280>{number = 9, name = (null)}
Operation task 1 end, thread = <NSThread: 0x600002b51980>{number = 7, name = (null)}
複製代碼
異步並不是執行而且全部任務開啓新線程。
OperationQueue 裏面有一個設置當前隊列最大操做併發執行的屬性 maxConcurrentOperationCount
private func queueActAsSerial() {
printOperation("Test begin")
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1 // 設置最大運行數目
queue.addOperation {
printOperation("Operation task 1 begin")
Thread.sleep(forTimeInterval: 3)
printOperation("Operation task 1 end")
}
let operationTask2 = BlockOperation {
printOperation("Operation task 2 begin")
Thread.sleep(forTimeInterval: 3)
printOperation("Operation task 2 end")
}
let operationTask3 = BlockOperation {
printOperation("Operation task 3 begin")
Thread.sleep(forTimeInterval: 3)
printOperation("Operation task 3 end")
}
queue.addOperation(operationTask2)
queue.addOperation(operationTask3)
printOperation("Test end")
}
複製代碼
輸出:
Test begin, thread = <NSThread: 0x600002b66d00>{number = 1, name = main}
Test end, thread = <NSThread: 0x600002b66d00>{number = 1, name = main}
Operation task 1 begin, thread = <NSThread: 0x600002b14080>{number = 3, name = (null)}
Operation task 1 end, thread = <NSThread: 0x600002b14080>{number = 3, name = (null)}
Operation task 2 begin, thread = <NSThread: 0x600002bf7a80>{number = 6, name = (null)}
Operation task 2 end, thread = <NSThread: 0x600002bf7a80>{number = 6, name = (null)}
Operation task 3 begin, thread = <NSThread: 0x600002bf7a80>{number = 6, name = (null)}
Operation task 3 end, thread = <NSThread: 0x600002bf7a80>{number = 6, name = (null)}
複製代碼
能夠當作此時就像是串行隊列同樣,只能等上一個任務完成才能執行下一個任務。這個最大併發數目在執行的時候最好是用默認值。它會由系統給出一個最優的值。在實際的設置中若是設置爲 maxConcurrentOperationCount = 1000 系統也是會根據實際狀況取最優值的。
private func operationDependenciesAndCompletionBlock() {
printOperation("Test begin")
let queue = OperationQueue()
queue.addOperation {
printOperation("Operation task 1 begin")
Thread.sleep(forTimeInterval: 3)
printOperation("Operation task 1 end")
}
let operationTask2 = BlockOperation {
printOperation("Operation task 2 begin")
Thread.sleep(forTimeInterval: 3)
printOperation("Operation task 2 end")
}
let operationTask3 = BlockOperation {
printOperation("Operation task 3 begin")
Thread.sleep(forTimeInterval: 3)
printOperation("Operation task 3 end")
}
let operationTask4 = BlockOperation {
printOperation("Operation task 4 begin")
Thread.sleep(forTimeInterval: 3)
printOperation("Operation task 4 end")
}
// 2 -> 4 - > 3
operationTask3.addDependency(operationTask4)
operationTask4.addDependency(operationTask2)
operationTask4.completionBlock = {
DispatchQueue.main.async {
printOperation("operationTask4 completion")
}
}
queue.addOperation(operationTask2)
queue.addOperation(operationTask3)
queue.addOperation(operationTask4)
printOperation("Test end")
}
複製代碼
輸出:
Test begin, thread = <NSThread: 0x600000bbebc0>{number = 1, name = main}
Test end, thread = <NSThread: 0x600000bbebc0>{number = 1, name = main}
Operation task 1 begin, thread = <NSThread: 0x600000b34000>{number = 6, name = (null)}
Operation task 2 begin, thread = <NSThread: 0x600000b2c440>{number = 7, name = (null)}
Operation task 1 end, thread = <NSThread: 0x600000b34000>{number = 6, name = (null)}
Operation task 2 end, thread = <NSThread: 0x600000b2c440>{number = 7, name = (null)}
Operation task 4 begin, thread = <NSThread: 0x600000b2c440>{number = 7, name = (null)}
Operation task 4 end, thread = <NSThread: 0x600000b2c440>{number = 7, name = (null)}
Operation task 3 begin, thread = <NSThread: 0x600000b34000>{number = 6, name = (null)}
operationTask4 completion, thread = <NSThread: 0x600000bbebc0>{number = 1, name = main}
Operation task 3 end, thread = <NSThread: 0x600000b34000>{number = 6, name = (null)}
複製代碼
能夠看出:
queue.waitUntilAllOperationsAreFinished()
這個會阻塞當前線程,直到隊列裏面全部 Operation 執行完畢。
private func queueCancel() {
printOperation("Test begin")
let queue = OperationQueue()
queue.addOperation {
printOperation("Operation task 1 begin")
Thread.sleep(forTimeInterval: 3)
printOperation("Operation task 1 end")
}
let operationTask2 = BlockOperation {
printOperation("Operation task 2 begin")
Thread.sleep(forTimeInterval: 3)
printOperation("Operation task 2 end")
}
let operationTask3 = BlockOperation {
printOperation("Operation task 3 begin")
Thread.sleep(forTimeInterval: 3)
printOperation("Operation task 3 end")
}
operationTask2.completionBlock = {
DispatchQueue.main.async {
printOperation("operationTask2 completion. isFinished = \(operationTask2.isFinished), isCancelled = \(operationTask2.isCancelled)")
}
}
queue.addOperation(operationTask2)
queue.addOperation(operationTask3)
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
printOperation("xxxx cancelAllOperations")
queue.cancelAllOperations()
}
printOperation("Test end")
}
複製代碼
輸出:
Test begin, thread = <NSThread: 0x600002d1ecc0>{number = 1, name = main}
Operation task 1 begin, thread = <NSThread: 0x600002d86b00>{number = 6, name = (null)}
Operation task 2 begin, thread = <NSThread: 0x600002d8ba40>{number = 7, name = (null)}
Operation task 3 begin, thread = <NSThread: 0x600002d68680>{number = 5, name = (null)}
Test end, thread = <NSThread: 0x600002d1ecc0>{number = 1, name = main}
xxxx cancelAllOperations, thread = <NSThread: 0x600002d1ecc0>{number = 1, name = main}
Operation task 2 end, thread = <NSThread: 0x600002d8ba40>{number = 7, name = (null)}
Operation task 1 end, thread = <NSThread: 0x600002d86b00>{number = 6, name = (null)}
Operation task 3 end, thread = <NSThread: 0x600002d68680>{number = 5, name = (null)}
operationTask2 completion. isFinished = true, isCancelled = true, thread = <NSThread: 0x600002d1ecc0>{number = 1, name = main}
複製代碼
經過設置 queue.isSuspended = true // or false
有時候一些關鍵資源須要保證線程安全,在 GCD 中可使用 DispatchSemaphore 使用,下面添加一個使用 NSLock 的例子
private var tickets = 12; // 12 張票
private var lock: NSLock!
private func queueWithThreadSafe() {
lock = NSLock()
let queue0 = OperationQueue()
let queue1 = OperationQueue()
// 添加畫不一樣隊列的不一樣操做
queue0.addOperation {
self.tikcetsSell()
}
queue0.addOperation {
self.tikcetsSell()
}
queue0.addOperation {
self.tikcetsSell()
}
queue1.addOperation {
self.tikcetsSell()
}
}
// 多個線程賣票,須要保證線程安全
private func tikcetsSell() {
while true {
lock?.lock() // 加鎖 ..... 在須要保存線程安全的代碼前加鎖
if tickets > 0 {
Thread.sleep(forTimeInterval: 0.3)
tickets -= 1
printOperation("Tickets count = \(tickets)")
}
lock?.unlock() // 去鎖 ..... 在須要保存線程安全的代碼後面去鎖
if tickets <= 0 {
printOperation("Tickets sold out")
break
}
}
}
複製代碼
輸出:
Tickets count = 11, thread = <NSThread: 0x60000145be80>{number = 7, name = (null)}
Tickets count = 10, thread = <NSThread: 0x60000145be80>{number = 7, name = (null)}
Tickets count = 9, thread = <NSThread: 0x60000145be80>{number = 7, name = (null)}
Tickets count = 8, thread = <NSThread: 0x60000145be80>{number = 7, name = (null)}
Tickets count = 7, thread = <NSThread: 0x60000145be80>{number = 7, name = (null)}
Tickets count = 6, thread = <NSThread: 0x60000145be80>{number = 7, name = (null)}
Tickets count = 5, thread = <NSThread: 0x60000145be80>{number = 7, name = (null)}
Tickets count = 4, thread = <NSThread: 0x60000145be80>{number = 7, name = (null)}
Tickets count = 3, thread = <NSThread: 0x60000145be80>{number = 7, name = (null)}
Tickets count = 2, thread = <NSThread: 0x60000145be80>{number = 7, name = (null)}
Tickets count = 1, thread = <NSThread: 0x60000145be80>{number = 7, name = (null)}
Tickets count = 0, thread = <NSThread: 0x60000145be80>{number = 7, name = (null)}
Tickets sold out, thread = <NSThread: 0x60000145be80>{number = 7, name = (null)}
Tickets sold out, thread = <NSThread: 0x6000014ba400>{number = 8, name = (null)}
Tickets sold out, thread = <NSThread: 0x6000014b7540>{number = 9, name = (null)}
Tickets sold out, thread = <NSThread: 0x6000014a8440>{number = 10, name = (null)}
複製代碼
可使用 NSLock 在 OperationQueue 裏面實現線程安全。
這裏有一個自定義 Operaion 的實際例子:Raywenderlich: Operation and OperationQueue Tutorial in Swift