NSOperation和NSOperationQueue學(xué)習(xí)總結(jié)

swift4.0版

1. NSOperation、NSOperationQueue 簡(jiǎn)介

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

GCD vs NSOperation Queue
GCD vs NSOperation Queue.png

我們可以看到,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)屬性isExecutingisFinished等。當(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 常用屬性和方法
  1. 取消操作方法:
    • (void)cancel; 可取消操作,實(shí)質(zhì)是標(biāo)記 isCancelled 狀態(tài)。
  2. 判斷操作狀態(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)。
  3. 操作同步:
    • (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 常用屬性和方法
  1. 取消/暫停/恢復(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ì)列。
  2. 操作同步:
    • (void)waitUntilAllOperationsAreFinished; 阻塞當(dāng)前線程,直到隊(duì)列中的操作全部執(zhí)行完畢。
  3. 添加/獲取操作:
    • (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ù)。
  4. 獲取隊(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é)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容