NSOperation與NSOperationQueue

與 GCD 不同的是,Operation Queues 不遵循先進先出的順序。以下是 Operation Queues 和 Dispatch Queues 的不同:

1: 不遵循 FIFO(先進先出):在 Operation Queues 中,你可以設置 operation(操作)的執行優先級,並且可以在 operation 之間添加依賴,這意味着你可以定義某些 operation,使得它們可以在另外一些 operation 執行完畢之後再被執行。這就是爲什麼它們不遵循先進先出的順序。
2: 默認情況下 Operation Queues 是併發執行:雖然你不能將其改成串行隊列,但還是有一種方法,通過在 operation 之間添加相依性來讓 Operation Queues 中的任務按序執行。
3: Operation Queues 是 NSOperationQueue 類的實例,任務被封裝在 NSOperation 的實例中。

NSOperation

任務是以 NSOperation 實例的形式被提交到 Operation Queues 中去的。之前說過 GCD 中任務是以 block 的形式被提交。在這裏也是類似,只不過是需要被綁到 NSOperation 實例中。你可以簡單的將 NSOperation 看作是一套工作任務的整體。

NSOperation 是一個抽象的類,不可以被直接拿來用,所以你只能使用 NSOperation 的子類。

NSBlockOperation - 用這個類來初始化包含一個或多個 blocks 的 operation。該 operation 本身可包含的 block 超過一個,當所有的block 執行完畢後這個 operation 就被視爲已完成。
NSInvocationOperation - 用這個類來初始化一個 operation,能用來調用某指定對象的選擇器(selector)。

首先它可以通過 NSOperation 類的 addDependency(op: NSOperation)方法獲得對相依性的支持。如果你有這樣的需求:即某 operation 的啓動需取決於另一個 operation 的執行,那麼就得用 NSOperation
你可以取消掉某特定隊列中的某個 operation,或者是取消隊列中所有的 operation。
通過調用 NSOperation 類的 cancel() 方法來實現對 operation 的取消。你取消任何 operation 的時候,會是下面三種場景之一:
1.你的 operation 已經完成了,這種情況下 cancel 方法沒有任何效果。
2. 你的 operation 正在被執行的過程中,這種情況下系統不會強制停止你的 operation 代碼,而是將 cancelled 屬性置爲 true。
3. 你的 operation 還在隊列中等待被執行,這種情況下你的 operation 就不會被執行。
4. NSOperation 有3個有用的布爾型屬性:finished,cancelled 和 ready。finished 在 operation 執行完畢後被置爲 true。cancelled 在 operation 被取消後被置爲 true。ready 在 operation 即將被執行時被置爲 true。
6. 所有的 NSOperation 在任務被完成後都可以選擇去設置一段 completion block。NSOperation 的 finished 屬性變爲 true 後這段 block 就會被執行。

  func testOperation() {
        
        let queue = OperationQueue()
        let imageURLs = ["https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/[email protected]",
        "https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/[email protected]", "https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/[email protected]","https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/[email protected]"]
        let imageView1 = UIImageView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
        
        let imageView2 = UIImageView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
        let imageView3 = UIImageView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
        let imageView4 = UIImageView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
        print("開始走")
        queue.addOperation {
            OperationQueue.main.addOperation {
                print("一個開始走")
                for num in 1..<100 {
                    print("first %@",num)
                }
                imageView1.loadingImage(withImageUrlStr: imageURLs[0])
                print("一個結束 %@",imageView1.image)
               
            }
        }
        
        queue.addOperation {
                OperationQueue.main.addOperation {
                       print("二個開始走")
                    for num in 1..<100 {
                       print("second %@",num)
                    }
                   imageView2.loadingImage(withImageUrlStr: imageURLs[1])
                   print("二個結束 %@",imageView2.image)
                }
        }
        queue.addOperation {
                OperationQueue.main.addOperation {
                       print("三個開始走")
                    for num in 1..<100 {
                       print("third %@",num)
                    }
                    imageView3.loadingImage(withImageUrlStr: imageURLs[2])
                    print("三個結束 %@",imageView3.image)
                }
        }
        queue.addOperation {
               OperationQueue.main.addOperation {
                       print("四個開始走")
                    for num in 1..<100 {
                       print("forth %@",num)
                    }
                    imageView4.loadingImage(withImageUrlStr: imageURLs[3])
                    print("四個結束 %@",imageView4.image)
             }
        }
       
        print("走完了所以流程")
        print("取消上面四個任務")
       queue.cancelAllOperations()
       
    }
    /*
    打印結果:
    開始走
	走完了所以流程
	取消上面四個任務
	如果我將 print("取消上面四個任務")
       queue.cancelAllOperations()放到一個結束的後面發現,全部都會打印,因爲這時已經把所有的operation加入到了operationQueue中了
    */

如上所見,你使用了 addOperationWithBlock 方法來創建一個新的、帶有某給定 block(或者它在 Swift 中的名字:閉包)的 operation。很簡單,對吧?爲在主隊列中完成某個任務,與使用 GCD 時調用 dispatch_async() 不同,我們用 NSOperationQueue(NSOperationQueue.mainQueue())也可以達到相同結果,將你想要在主隊列中執行的 operation 提交過去。
可以運行一下這個 App 做個快速測試。如果代碼輸入正確的話, App 應該能夠在後臺下載圖片,不會阻塞UI。

之前的例子中我們使用了 addOperationWithBlock 方法把 operation 添加到隊列中。再讓我們來看看如何使用 NSBlockOperation:在達到相同的效果的同時,還會給我們提供更多的功能和選項,比如設置 completion handler。

  func testOperation() {
        
        let queue = OperationQueue()
        let imageURLs = ["https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/[email protected]",
        "https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/[email protected]", "https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/[email protected]","https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/[email protected]"]
        let imageView1 = UIImageView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
        let imageView2 = UIImageView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
        print("開始走")
        let operation1 = BlockOperation {
                print("一個開始走")
                let url = URL(string: imageURLs[0])
                           let data = try? Data(contentsOf: url!)
                DispatchQueue.main.async {
                      imageView1.image = UIImage(data: data!)
                 }
           print("一個結束")
        }
     
        
        let operation2 = BlockOperation {
              
          print("二個開始走")
          let url = URL(string: imageURLs[1])
           let data = try? Data(contentsOf: url!)
            DispatchQueue.main.async {
                imageView2.image = UIImage(data: data!)
            }
             print("二個結束")
        }
       
        operation1.addDependency(operation2)
        operation1.completionBlock = {
             print("operation 1 completed")
        }
        operation2.completionBlock = {
            print("operation 2 completed")
         }
         queue.addOperation(operation1)
         queue.addOperation(operation2)
         print("走完了所以流程") 
    }
    /*打印結果
    開始走
	走完了所以流程
	二個開始走
	二個結束
	一個開始走
	operation 2 completed
	一個結束
	operation 1 completed
    */

不添加依賴正常打印

  func testOperation() {
        
        let queue = OperationQueue()
        let imageURLs = ["https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/[email protected]",
        "https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/[email protected]", "https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/[email protected]","https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/[email protected]"]
        let imageView1 = UIImageView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
        let imageView2 = UIImageView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
        print("開始走")
        let operation1 = BlockOperation {
                print("一個開始走")
                let url = URL(string: imageURLs[0])
                           let data = try? Data(contentsOf: url!)
                DispatchQueue.main.async {
                      imageView1.image = UIImage(data: data!)
                 }
           print("一個結束")
        }
     
        
        let operation2 = BlockOperation {
              
          print("二個開始走")
          let url = URL(string: imageURLs[1])
           let data = try? Data(contentsOf: url!)
            DispatchQueue.main.async {
                imageView2.image = UIImage(data: data!)
            }
             print("二個結束")
        }

        operation1.completionBlock = {
             print("operation 1 completed")
        }
        operation2.completionBlock = {
            print("operation 2 completed")
         }
         queue.addOperation(operation1)
         queue.addOperation(operation2)
         print("走完了所以流程") 
    }
/*
開始走
走完了所以流程
一個開始走
二個開始走
一個結束
operation 1 completed
二個結束
operation 2 completed
*/

由於 operation #1 已經開始執行,取消對它沒有任何效果。這就是爲什麼 cancelled 會被記錄成 false,並且 App 還是會顯示第一張圖片。
operation #2 會被取消。對 cancelAllOperations() 的調用會停止對該 operation 的執行,所以第二張圖片沒有被下載。
operation #3 已經排在隊列中,等待 operation #2 的完成。因爲 operation #3 是否開始取決於 operation #2 的完成與否,而 operation #2 已經被取消,operation #3 就不會被執行,從隊列中被立即踢出了,同理operation#4也不會被執行。

    func testOperation() {
        
        let queue = OperationQueue()
        let imageURLs = ["https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/[email protected]",
        "https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/[email protected]", "https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/[email protected]","https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/[email protected]"]
        let imageView1 = UIImageView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
        
        let imageView2 = UIImageView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
       let imageView3 = UIImageView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
        let imageView4 = UIImageView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
        print("開始走")
        let operation1 = BlockOperation {
     
                print("一個開始走")
                let url = URL(string: imageURLs[0])
                           let data = try? Data(contentsOf: url!)
                DispatchQueue.main.async {
                      imageView1.image = UIImage(data: data!)
                 }
               print("一個結束")
        }
     
        let operation2 = BlockOperation {
              
          print("二個開始走")
          let url = URL(string: imageURLs[1])
           let data = try? Data(contentsOf: url!)
            DispatchQueue.main.async {
                imageView2.image = UIImage(data: data!)
            }
             print("二個結束")
        }
        let operation3 = BlockOperation {
              
          print("三個開始走")
          let url = URL(string: imageURLs[2])
           let data = try? Data(contentsOf: url!)
            DispatchQueue.main.async {
                imageView3.image = UIImage(data: data!)
            }
             print("三個結束")
        }
        
        let operation4 = BlockOperation {
              
          print("四個開始走")
                             
          let url = URL(string: imageURLs[3])
           let data = try? Data(contentsOf: url!)
            DispatchQueue.main.async {
                imageView3.image = UIImage(data: data!)
            }
             print("四個結束")
        }
        peration2.addDependency(operation1)
         operation3.addDependency(operation2)
         operation4.addDependency(operation3)
        
        operation1.completionBlock = {
            queue.cancelAllOperations()
            print("Operation 1 completed, cancelled:\(operation1.isCancelled) ")
         }
        operation2.completionBlock = {
                   print("operation 2 completed")
         }
        operation3.completionBlock = {
                         print("operation 3 completed")
         }
        operation4.completionBlock = {
                         print("operation 4 completed")
        }
         queue.addOperation(operation1)
         queue.addOperation(operation2)
        queue.addOperation(operation3)
        queue.addOperation(operation4)
        
        print("走完了所以流程")
   
       
    }

GCD和NSOperation的區別:

GCD原理:系統有個線程池,用於分配和回收線程,程序員不需要對線程管理
GCD的高級功能:延遲執行after,一次性執行once(單例),調度組(實現批量處理)
GCD存在四種組合,NSOperation只有全局隊列、異步操作,如果想同步,必須用依賴關係
NSOperation的高級:最大併發數,控制線程個數,優化了線程的暫停、繼續、取消功能(GCD實現起來太難),依賴關係,可以讓異步任務同步執行

  1. GCD是底層的C語言構成的API,而NSOperationQueue及相關對象是Objc的對象。在GCD中,在隊列中執行的是由block構成的任務,這是一個輕量級的數據結構;而Operation作爲一個對象,爲我們提供了更多的選擇;
  2. 在NSOperationQueue中,我們可以隨時取消已經設定要準備執行的任務(當然,已經開始的任務就無法阻止了),而GCD沒法停止已經加入queue的block(其實是有的,但需要許多複雜的代碼);
  3. NSOperation能夠方便地設置依賴關係,我們可以讓一個Operation依賴於另一個Operation,這樣的話儘管兩個Operation處於同一個並行隊列中,但前者會直到後者執行完畢後再執行;
  4. 我們能將KVO應用在NSOperation中,可以監聽一個Operation是否完成或取消,這樣子能比GCD更加有效地掌控我們執行的後臺任務;
  5. 在NSOperation中,我們能夠設置NSOperation的priority優先級,能夠使同一個並行隊列中的任務區分先後地執行,而在GCD中,我們只能區分不同任務隊列的優先級,如果要區分block任務的優先級,也需要大量的複雜代碼;
  6. 我們能夠對NSOperation進行繼承,在這之上添加成員變量與成員方法,提高整個代碼的複用度,這比簡單地將block任務排入執行隊列更有自由度,能夠在其之上添加更多自定製的功能。
  7. GCD 是嚴格的隊列,先進先出 FIFO;NSOperation可以改動 優先級(或者說服務質量)改變執行順序

NSOperation的高級:最大併發數,控制線程個數,優化了線程的暫停、繼續、取消功能(GCD實現起來太難,可以用 KVO ),依賴關係,可以讓異步任務同步執行

NSOperationQueue暫停&繼續&取消 可以控制操作的執行
NSOperationQueue 暫停和恢復隊列

  // YES代表暫停隊列,NO代表恢復隊列
  @property (getter=isSuspended) BOOL suspended;
  - (void)viewDidLoad {
      [super viewDidLoad];

      _queue = [[NSOperationQueue alloc] init];
      _queue.maxConcurrentOperationCount = 2;
  }

  /**
   *  創建多個操作 開始執行
   *  @param sender
   */
  - (IBAction)start:(id)sender {
      int count = 15;

      for (int i = 0; i < count; i++) {
          NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
              [NSThread sleepForTimeInterval:1];
              NSLog(@"i = %d, thread = %@", i, [NSThread currentThread]);
          }];

          [_queue addOperation:operation];
      }
  }

  /**
   *  將隊列掛起,暫停執行隊列中還沒有執行的 操作
   *  @param sender
   */
  - (IBAction)suspended:(id)sender {
      if (!_queue.suspended) {
          // operationCount 包含正在執行的 操作
          NSLog(@"掛起隊列, 當前隊列中還有%zd個任務", _queue.operationCount);
          // 掛起任務,沒有執行的操作纔會暫停,已經在執行的操作會繼續執行
          _queue.suspended = YES;
      }
  }

  - (IBAction)resume:(id)sender {
      if (_queue.suspended) {
          NSLog(@"當前隊列中還有%zd個任務", _queue.operationCount);
          _queue.suspended = NO;
      }
  }

注意: 向 掛起 的 隊列 添加 操作, 添加的 操作 是 不會執行 的

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