Operation、OperationQueue的使用

iOS、macOS 設備一般爲多核,也就是可以同時執行多項任務。將代碼分爲多塊,併發(Concurrency)執行可以提高 app 流程性。

iOS、macOS 提供了Grand Central Dispatch(簡稱GCD)、Operations兩種解決方案。我的另一篇文章Grand Central Dispatch的使用已經介紹過GCD,這篇文章將介紹Operation、OperationQueue和BlockOperation。

1. Operations

Operation代表單個任務代碼和數據的抽象類。

因爲Operation是抽象類,所以不能直接使用。需繼承後使用其子類,或使用系統提供的子類來執行任務。例如NSInvocationOperationNSBlockOperation。儘管Operation是抽象類,但其實現了協調任務執行的邏輯,使用其子類時只需關注任務執行即可,無需關注任務與其他系統對象關係。

operation 對象只能執行一次。通常,將 operation 對象添加到OperationQueue類的實例中,OperationQueue類實例可以直接在輔助線程執行 operation,或者使用 GCD 執行。

1.1 Operation 狀態

Operation對象在內部維護了狀態(state)信息,以便決定何時可以執行任務,以及通知外部客戶當前任務的生命週期。子類需維護 state 信息,以確保正確執行代碼中的操作。與Operationstate 相關 key path 如下:

  • isReady:Operation是否可以執行。可以執行時爲 true,不能執行時爲 false。大部分情況下,無需維護isReady。如果除 dependent 外還有其他條件約束是否可執行,則需要實現 isReady,並維護其狀態。

  • isExecuting:Operation是否正在執行任務。正在執行時爲 true,否則爲 false。

    如果實現了start()方法,則需實現 isExecuting 屬性,並在狀態變化時發送 KVO 通知。

  • isFinished:標記 operation 成功執行完成,或取消退出。只有當 operation 的 isFinished 爲 true 時,operation 對象纔會移除 dependency,operation queue 纔會出隊 operation。因此,任務完成後將 operation 的 isFinished 標記爲 true 至關重要。

    如果實現了start()方法,則需實現 isFinised 屬性,並在狀態變化時發送 KVO 通知。

  • isCancelled:標記 operation 是否被取消。推薦但並非必須實現 isCancelled,其狀態變化時沒有必要發出 KVO 通知。

上述狀態均是隻讀的,在任務執行過程中隨時可以查詢這些屬性狀態。Operation類自動維護這些狀態。但你可以通過調用start()改變 isExecuting 狀態,調用cancel()改變 isCancelled 狀態。

1.2 BlockOperation

BlockOperationOperation的具體子類,用於管理一個、多個塊的併發執行。使用BlockOperation類可以一次執行多個塊,無需爲每個塊創建單獨的 operation。當執行多個 block 時,只有所有 block 執行完畢時,BlockOperation才視爲完成,這一點與dispatch group很像。

默認情況下,添加到BlockOperation中的 block 被調度到默認全局隊列。Block 不應對運行環境做任何假設。

1.2.1 單個 block 的BlockOperation

創建包含一個 block 的BlockOperation非常簡單,只需爲初始化方法傳遞 closure 即可。如下所示:

        let operation = BlockOperation {
            print("7 + 11 = \(7 + 11)")
        }
        operation.start()
1.2.2 多 block 的 BlockOperation

調用addExecutionBlock方法可以向BlockOperation添加多個block。如果調用時,BlockOperation實例正在執行或已經完成,則會拋出NSInvalidArgumentException的異常。

    private func testMultipleBlockOperation() {
        let sentence = "The author of this article is pro648"
        let wordOperation = BlockOperation()
        
        for word in sentence.split(separator: " ") {
            wordOperation.addExecutionBlock {
                print(word)
            }
        }
        
        wordOperation.start()
    }

上述代碼將sentence語句按空格分割,將每個單詞添加到一個 closure。執行後每行輸出一個單詞,多次執行會發現其先後順序不固定。

更新上述代碼如下:

    private func testMultipleBlockOperation() {
        let sentence = "The author of this article is pro648"
        let wordOperation = BlockOperation()
        
        for word in sentence.split(separator: " ") {
            wordOperation.addExecutionBlock {
                print(word)
                sleep(2)
            }
        }
        
        let interval = duration {
            wordOperation.start()
        }
        print("Timeinterval: \(interval)")
    }
    
    private func duration(_ block: () -> ()) -> TimeInterval {
        let startTime = Date()
        block()
        return Date().timeIntervalSince(startTime)
    }

儘管每個操作後休眠2秒鐘,但其總時間並不是14秒,如下所示:

author
of
this
The
is
article
pro648
Timeinterval: 4.002905011177063

BlockOperation與 DispatchGroup 有些相似,都可以很簡單的獲取所有任務完畢的消息。如果爲 wordOperation 添加completionBlock,其會在所有任務執行完畢後調用。在調用duration前添加以下代碼:

        wordOperation.completionBlock = {
            print("https://github.com/pro648/tips/wiki")
        }

運行後輸出如下:

this
of
author
The
article
is
pro648
https://github.com/pro648/tips/wiki
Timeinterval: 4.002982020378113

1.3 Operation 子類

BlockOperation類適用於簡單任務,如果要執行復雜、複用的任務,需使用Operation的子類。

通過後續章節的練習,將實現如下圖所示的demo:

其從網絡下載圖片、添加濾鏡,分別封裝到不同 operation,且添加濾鏡 operation 依賴圖片下載 operation。

https://github.com/pro648/BasicDemos-iOS/tree/master/Operation&OperationQueue下載demo源碼。

更新tableView(_:cellForRowAt:)方法,使用SepiaFilter類的applySepiaFilter(_:)方法爲圖片添加濾鏡:

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "PhotoCell", for: indexPath) as! PhotoCell
        let inputImage = UIImage(named: "\(indexPath.row).png")!
//        cell.display(image: inputImage)
        
        guard let filteredImage = SepiaFilter().applySepiaFilter(inputImage) else {
            cell.display(image: nil)
            return cell
        }
        cell.display(image: filteredImage)
        
        return cell
    }

添加濾鏡後滑動 app 可以看到明顯掉幀。爲了讓 app 更流暢,需要將添加濾鏡的操作轉移到其他線程。

Core Image 相關操作會重複使用,應放到Operation子類中進行。子類需要inputImage和outputImage兩個屬性,inputImage應禁止修改。創建FilterOperation類,如下所示:

final class FilterOperation: Operation {
    var outputImage: UIImage?
    
    private let inputImage: UIImage
    
    init(image: UIImage) {
        inputImage = image
        super.init()
    }
}

如果將其設置爲屬性,則每個FilterOperation實例都會初始化一份 context。CIContext是線程安全的,應儘可能重複使用,static 關鍵字可以實現這一需求。在SepiaFilter類開始位置添加以下代碼:

    private static let context = CIContext()

operation 開始時會調用main()方法。main()函數的默認實現不執行任何操作,重寫此方法以執行所需任務,請勿在此方法中調用 super。更新後如下:

    override func main() {
        guard let filteredImage = SepiaFilter().applySepiaFilter(inputImage) else {
            print("Failed to apply sepia filter")
            return
        }
         outputImage = filteredImage
    }

現在,更新tableView(_:cellForRowAt:)方法,使用FilterOperation類添加濾鏡:

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "PhotoCell", for: indexPath) as! PhotoCell
        let inputImage = UIImage(named: "\(indexPath.row).png")!
        
        // 使用FilterOperation添加濾鏡
        print("Filtering")
        let op = FilterOperation(image: inputImage)
        op.start()
        
        cell.display(image: op.outputImage)
        print("Done")
        
        return cell
    }

在上述代碼中,手動調用start()方法執行 operation。運行demo後會發現性能沒有發生變化,依然掉幀嚴重。

Operation對象默認是同步執行,即 operation 對象不會創建單獨線程執行任務。當調用start()方法時,operation 在當前線程執行,當start()方法將控制權返回給調用者時,任務已經完成。在這裏,operation 任務在主線程執行,並不能提升性能。

調用start()除在當前線程執行任務,如果 operation 狀態不是 ready,還會引起閃退。通常,不會直接調用start()

2. OperationQueue

OperationQueue根據隊列中Operation優先級、是否可執行調度執行。添加到OperationQueue後,operation 一直處於隊列中直到完成。不能直接將 operation 從 operation queue 移除。

OperationQueue持有 operation,直到 operation 執行完成。queue 持有自身直到所有任務完成。operation 未完成時掛起 operation queue 可能產生內存泄漏。

使用OperationQueue處理Operation時,Operation的優勢才得以體現。像 GCD 的 DispatchQueue 一樣,OperationQueue類用於管理Operation調度、最大執行數量等。

有以下三種向OperationQueue添加任務方式:

  • 傳遞 operation。
  • 傳遞 closure。
  • 傳遞 operation 數組。

2.1 常用功能點

OperationQueue根據 operation 的ready、quality of service 和 denpendencies 決定執行順序。一旦將 operation 添加到OperationQueue,其會一直執行,直到完成或取消。後續章節會介紹 dependencies 和 cancelling operation。

一旦將Operation添加到了OperationQueue,則不能將其添加到任何其他隊列。Operation實例是一次性任務,這也是爲什麼將其寫爲子類,以便可以多次執行。

2.1.1 waitUntilAllOperationsAreFinished

堵塞當前線程,直到 queue 正在執行、等待執行的所有任務執行完畢。在執行期間,當前線程不能向 queue 添加 operation,但其他線程可以。

當所有任務執行完畢,方法纔會完成。如果 queue 中沒有 operation,方法立即返回。

由於會堵塞當前線程,請勿在主線程中調用該方法。如果需要使用該方法需創建串行隊列,以便安全的調用該方法。如果只需要等待部分 operation 完成,可以使用addOperations(_:waitUntilFinished:)方法。

2.1.2 qualityOfService

該屬性指定 queue 中 operation 默認優先級。如果 operation 對象已經設置了優先級,則採用 operation 對象的優先級。

該屬性默認值與創建 queue 方式相關。使用main()方法創建的 queue,默認值爲NSOperationQualityOfServiceUserInteractive,且不可更改;自己創建的隊列默認值爲NSOperationQualityOfServiceBackground

Service level 決定 operation 可獲得 CPU 時間、網絡、磁盤資源的優先級。qualityOfService級越高,獲得的資源越多,任務可以執行的越快。使用qualityOfService可以確保顯式任務優先級高於次要任務。

2.1.3 isSuspended

isSuspended用於標記 queue 是否正在調度 operation 的執行。

isSuspended值爲 false 時,queue 調度 operation 執行。其值爲 true 時,將不再調度 operation 執行,但已開始的任務會繼續執行。可以向已暫停的 queue 添加 operation,但直到isSuspended變爲 true 纔會開始執行。

使用 KVO 可以觀察該屬性。該屬性默認爲 false。

2.1.4 underlyingQueue

underlyingQueue屬性默認值爲 nil。可以將已經存在的 dispatch queue 分配給該屬性,以便 queue 中所有 operation 都在underlyingQueue指定的隊列中執行。

只有當OperationQueue隊列中沒有 operation 時纔可以設置該屬性,當operationCount不爲0時設置該屬性會觸發invalidArgumentException異常。不能將dispatch_get_main_queue()分配給underlyingQueue

Dispatch queue 的qualityOfService會重寫OperationQueuequalityOfService

2.2 使用 OperationQueue

ViewController.swift文件添加以下屬性:

    private let queue = OperationQueue()

更新tableView(_:cellForRowAt:)方法如下:

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "PhotoCell", for: indexPath) as! PhotoCell
        let inputImage = UIImage(named: "\(indexPath.row).png")!
        
        // 將Operation添加到OperationQueue
        let op = FilterOperation(image: inputImage)
        op.completionBlock = {
            DispatchQueue.main.async {
                guard let cell = tableView.cellForRow(at: indexPath) as? PhotoCell else { return }
                
                cell.isLoading = false
                cell.display(image: op.outputImage)
            }
        }
        queue.addOperation(op)
        
        return cell
    }

現在,將 operation 添加到了OperationQueue,其會自動在後臺線程執行,不會堵塞當前線程。當 operation 執行完畢,會調用compleitonBlockcompletionBlock不包含參數,沒有返回值。

3. Asynchronous Operation

截至目前,operation 都是同步執行的。當 operation 變爲 isReady 狀態時,OperationQueue獲知了這一改變,並開始搜索可用線程。

當調度查找到可用線程時,operation 變爲isExecuting狀態。此時,operation 開始執行。執行結束狀態變爲isFinished

執行異步 operation 時,main()方法會調用異步任務,之後main()運行完成。但此時 operation 不應進入isFinished

3.1 創建 AsyncOperation

將異步方法封裝進 operation 需要管理 state 變化。

現在,創建一個異步 operation 的基類,以後所有異步 operation 都可以繼承自該類。

3.1.1 State

由於Operation的 state 是隻讀的,需創建一種可讀可寫的記錄狀態方式。在AsyncOperation文件添加以下枚舉:

    enum State: String {
        case ready, executing, finished
        
        fileprivate  var keyPath: String {
            return "is\(rawValue.capitalized)"
        }
    }

創建State屬性,並添加 property observer。代碼如下:

    var state = State.ready {
        willSet {
            willChangeValue(forKey: newValue.keyPath)
            willChangeValue(forKey: state.keyPath)
        }
        didSet {
            didChangeValue(forKey: oldValue.keyPath)
            didChangeValue(forKey: state.keyPath)
        }
    }
3.1.2 Base Property

重寫常見屬性:

    override var isReady: Bool {
        return super.isReady && state == .ready
    }
    
    override var isExecuting: Bool {
        return state == .executing
    }
    
    override var isFinished: Bool {
        return state == .finished
    }

isReady需先檢查父類的isReady

最後重寫isAsynchronous屬性:

    override var isAsynchronous: Bool {
        return true
    }
3.1.3 start()

手動開始任務,或添加任務到 queue 後,最先調用的是start()方法。其負責調用main()方法。

    override func start() {
        main()
        state = .executing
    }

重寫start()方法時,不能調用super.start()

由於這裏是異步 operation,main()方法立即返回,但其並未執行完畢,需將 state 設置爲.executing

如果 Swift 有抽象類的概念,AsyncOperation 就是這種類型。

3.2 使用 AsyncOperation

目前,爲本地圖片添加濾鏡,下面將其改爲網絡圖片。

創建NetworkImageOperation.swift文件,用於下載資源。這裏不僅可下載圖片,還可作爲可複用組建。其應滿足以下四點:

  1. 初始化方法傳入 URL,或可轉爲 URL 的 String。
  2. 從傳入 URL 下載資源。
  3. 如果有 completion handler,則調用。
  4. 如果沒有 completion handler 且爲圖片,設置圖片。

使NetworkImageOperation繼承自AsyncOperation,並聲明所需變量:

import UIKit

typealias ImageOperationCompletion = ((Data?, URLResponse?, Error?) -> Void)?

final class NetworkImageOperation: AsyncOperation {
    var image: UIImage?
    
    private let url: URL
    private let completion: ImageOperationCompletion
}

初始化方法如下:

    init(url: URL, completion: ImageOperationCompletion = nil) {
        self.url = url
        self.completion = completion
        
        super.init()
    }
    
    convenience init?(string: String, completion: ImageOperationCompletion = nil) {
        guard let url = URL(string: string) else { return nil }
        self.init(url: url, completion: completion)
    }

重寫main()方法,實現下載任務:

    override func main() {
        URLSession.shared.dataTask(with: url) { [weak self] (data, response, error) in
            guard let self = self else { return }
            
            defer {
                self.state = .finished
            }
            
            if let completion = self.completion {
                completion(data, response, error)
                return
            }
            
            guard error == nil, let data = data else { return }
            
            self.image = UIImage(data: data)
        }.resume()
    }

由於這裏是異步任務,需使用 weak 解決任務還未完成對象已被釋放的問題。使用defer確保任務始終會被標記爲完成。

更新tableView(_:cellForRowAt:),使用NetworkImageOperation下載網絡圖片:

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "PhotoCell", for: indexPath) as! PhotoCell
        cell.display(image: nil)
        
        // 使用網絡圖片
        let op = NetworkImageOperation(url: urls[indexPath.row  ])
        op.completionBlock = {
            DispatchQueue.main.async {
                guard let cell = tableView.cellForRow(at: indexPath) as? PhotoCell else { return }
                
                cell.isLoading = false
                cell.display(image: op.image)
            }
        }
        queue.addOperation(op)
        
        return cell
    }

運行後可以看到從網絡獲取的圖片。

4. Operation Dependencies

使用 dependencies 可以便捷管理 operation 執行順序。使用addDependency(_:)removeDependency(_:)方法管理依賴。operation 的所有 dependencies 都執行完畢後,operation 纔會成爲 ready。

operation 執行完畢或取消,都被認爲完成。如果只有 dependencies 執行成功才執行 operation,則需添加額外代碼跟蹤執行情況。

Dependencies 有以下兩個優點:

  1. 只有特定 operation 執行完畢纔開始執行當前 operation。
  2. 提供了第一個 operation 與第二個 operation 間傳遞數據的簡便方法。

Operation可以指定 dependencies 是選取Operation而非 GCD 的原因之一。

4.1 模塊原則

目前 demo 有下載圖片 operation、添加濾鏡 operation。也可以創建一個 operation,同時執行下載圖片和添加濾鏡,但這不是一個好的架構設計。

每個類應只執行一項任務,以便複用。如果將下載圖片和添加濾鏡合併到一個 operation,則不能爲 bundle 內圖片添加濾鏡。也可以爲初始化方法添加很多參數,用以區分使用本地圖片和下載圖片,但這會讓類變得臃腫,難以維護。

4.2 指定 dependencies

假設需要下載圖片、解碼、添加濾鏡:

        let networkOp = NetworkImageOperation()
        let decryptOp  = DecryptOperation()
        let filterOp = FilterOperation()
        
        decryptOp.addDependency(networkOp)
        filterOp.addDependency(decryptOp)

如果需要移除 dependency,只需調用以下代碼:

        filterOp.removeDependency(decryptOp)

Operation類也提供了只讀數組類型的dependencies屬性,用於返回接收者的依賴。

如果使用 GCD 解決上述問題,僞代碼如下:

        let network = NetworkClass()
        network.onDownloaded { raw in
            guard let raw = raw else { return }
            
            let decrypt = DecryptClass(raw)
            decrypt.onDecrypted { decrypted in
                guard let decrypted = decrypted else { return }
                
                let tilt = TiltShiftClass(decrypted)
                tilt.onTiltShifted { tilted in
                    guard let tilted = tilted else { return }
                }
            }
        }

GCD 處理這一問題時,不易於處理每個操作的錯誤,也不利於後期維護。

4.3 死鎖

當 A 依賴 B,B 依賴 A 時,會產生死鎖。如果依賴關係是直線型的,不會產生循環,則不會產生死鎖。

一個 operation queue 的 operation 可以依賴另一個 queue 的 operation,只要沒有循環,也不會產生 deadlock。

如果產生了循環,則會造成死鎖:

在上圖中,op2 依賴 op5,op5 依賴 op3,op3 依賴 op2。開始與結束 opaeration 相同,造成了死鎖。

4.4 Operation 間傳遞數據

Operation 的優勢在於封裝和複用。不同 operation 可能使用不同名字屬性輸出圖片,使用 protocol 可以解決這一問題。

4.4.1 Protocol

operation 執行完畢使用 protocol 提供可能存在的圖片。

創建ImageDataProvider.swift文件,添加以下代碼:

import UIKit

protocol ImageDataProvider {
    var image: UIImage? { get }
}

每個有輸出圖片的 operation 均需遵守ImageDataProvider協議。

4.4.2 添加 extension

NetworkImageOperation類底部實現ImageDataProvider協議:

extension NetworkImageOperation: ImageDataProvider {
    
}

由於NetworkImageOperation類輸出圖片名稱和ImageDataProvider名稱一致,這裏無需處理。

FilterOperation類底部實現ImageDataProvider協議:

extension FilterOperation: ImageDataProvider {
    var image: UIImage? {
        return outputImage
    }
}

extension 可以添加到任何位置、文件,使用 extension 可以修改第三方庫。

4.4.3 採用 dependencies

FilterOperation類初始化時需要傳入圖片,還可以通過 dependencies 查找圖片。

更新FilterOperation類的main()方法、init(image:)方法,在參數 image 爲空時從 dependencies 查找圖片:

    private let inputImage: UIImage?
    
    init(image: UIImage? = nil) {
        inputImage = image
        super.init()
    }
    
    override func main() {
        let dependencyImage = dependencies.compactMap { ($0 as? ImageDataProvider)?.image }.first
        
        guard let inputImage = inputImage ?? dependencyImage else { return }
        
        guard let filteredImage = SepiaFilter().applySepiaFilter(inputImage) else {
            print("Failed to apply sepia filter")
            return
        }
         outputImage = filteredImage
    }

4.5 使用 dependencies

使用前面的 download operation 下載圖片,使用 filter operation 添加濾鏡,並指定 filter operation 的依賴爲 download operation。

更新tableView(_:atIndexPathFor:)方法如下:

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "PhotoCell", for: indexPath) as! PhotoCell
        cell.display(image: nil)
        
        let downloadOp = NetworkImageOperation(url: urls[indexPath.row])
        let filterOp = FilterOperation()
        filterOp.completionBlock = {
            DispatchQueue.main.async {
                guard let cell = tableView.cellForRow(at: indexPath) as? PhotoCell else { return }
                
                cell.isLoading = false
                cell.display(image: filterOp.image)
            }
        }
        filterOp.addDependency(downloadOp)
        
        queue.addOperation(downloadOp)
        queue.addOperation(filterOp)
        
        return cell
    }

雖然指定了 filterOp 依賴 downloadOp,但雙方都需添加到 queue。

5. 取消 Operation

將 operation 添加到 queue 後,queue 負責調度執行。如果不再需要執行可以取消 operation,以節省 CPU 時間。調用cancel()取消單個 operation,調用cancelAllOperations()取消OperationQueue的所有 operation。

任務取消後,isCancelled屬性會立即變爲 true,但並不會立即終止任務。Operation類默認支持取消。如果在調用start()函數前取消了任務,start()函數執行時會立即結束任務。

5.1 執行前取消

這一部分更新AsyncOperationstart()方法,使其支持取消:

    override func start() {
        if isCancelled {
            state = .finished
            return
        }
        
        main()
        state = .executing
    }

更新後,operation 開始前可以取消。

5.2 執行過程中取消

爲了執行過程中取消,需多次檢查isCancelled狀態。

打開NetworkImageOperation.swift文件,在main()方法內defer後添加以下判斷:

            guard !self.isCancelled else { return }

對於下載資源,只判斷一次即可。

添加以下屬性,以便取消網絡任務:

    private var task: URLSessionDataTask?

更新main()方法,利用上述 task 下載資源:

    override func main() {
        task = URLSession.shared.dataTask(with: url) { [weak self] (data, response, error) in
            ...
        }
        task?.resume()
    }

最後,重寫cancel()方法取消任務:

    override func cancel() {
        super.cancel()
        task?.cancel()
    }

FilterOperation類也需支持取消,這裏不再詳述,可以在文章底部獲取源碼查看。

5.3 具體應用

Table view cell 劃出可見範圍時,應取消 download operation、filter operation。

打開ViewController.swift文件,在tableView(_:cellForRowAt:)方法return cell前添加以下代碼:

        if let exisingOperations = operations[indexPath] {
            for operation in exisingOperations {
                operation.cancel()
            }
        }
        operations[indexPath] = [downloadOp, filterOp]

如果 indexPath 對應 operation 已經存在,先取消。

cell 劃出可見範圍時,會調用tableView(_:didEndDisplaying:cellForRowAt:)方法,在該方法內取消劃出cell的operation:

    func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        if let exisingOperations = operations[indexPath] {
            for operation in exisingOperations {
                operation.cancel()
            }
        }
    }

運行demo,或許看不出太多變化,但劃過的cell不會再繼續下載圖片、添加濾鏡。

總結

GCD 和 Operations 都可以將代碼塊提交到單獨線程上執行,但 operations 可以更好控制提交的任務。Operations 是在 GCD 基礎上的封裝,增加了額外的功能。例如,依賴其他操作,取消正在執行的操作。其更適用於會複用的操作。如果是一次性功能,請選擇 GCD。

如果你對多線程、併發、鎖還不瞭解,可以查看我的以下文章:

  1. 多線程簡述
  2. 併發控制之線程同步
  3. 併發控制之無鎖編程

Demo名稱:Operation&OperationQueue
源碼地址:https://github.com/pro648/BasicDemos-iOS/tree/master/Operation&OperationQueue

參考資料:

  1. Operation
  2. OperationQueue
  3. NSOperation
  4. Operation and OperationQueue Tutorial in Swift

歡迎更多指正:https://github.com/pro648/tips

本文地址:https://github.com/pro648/tips/blob/master/sources/Operation、OperationQueue的使用.md

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章