NSOperation和NSOperationQueue

NSOperation和NSOperationQueue

NSOperation 是蘋果公司對 GCD 的封裝,完全面向?qū)ο螅允褂闷饋砀美斫狻?大家可以看到 NSOperationNSOperationQueue 分別對應 GCD 的 任務 和 隊列 。操作步驟也很好理解:

  1. 將要執(zhí)行的任務封裝到一個 NSOperation 對象中。
  2. 將此任務添加到一個 NSOperationQueue 對象中。

然后系統(tǒng)就會自動在執(zhí)行任務。至于同步還是異步、串行還是并行請繼續(xù)往下看:

添加任務

值得說明的是,NSOperation 只是一個抽象類,所以不能封裝任務。但它有 2 個子類用于封裝任務。分別是:NSInvocationOperationNSBlockOperation 。創(chuàng)建一個 Operation 后,需要調(diào)用 start 方法來啟動任務,它會 默認在當前隊列同步執(zhí)行。當然你也可以在中途取消一個任務,只需要調(diào)用其 cancel 方法即可。

  • NSInvocationOperation : 需要傳入一個方法名。
    OBJECTIVE-C
      //1.創(chuàng)建NSInvocationOperation對象
      NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];

      //2.開始執(zhí)行
      [operation start];
SWIFT

在 `Swift` 構(gòu)建的和諧社會里,是容不下 NSInvocationOperation 這種不是類型安全的敗類的。蘋果如是說。這里有[相關(guān)解釋](http://stackoverflow.com/questions/26644477/nsinvocationoperation-is-unavailable-in-xcode-6-1)
  • NSBlockOperation
    OBJECTIVE-C
      //1.創(chuàng)建NSBlockOperation對象
      NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
          NSLog(@"%@", [NSThread currentThread]);
      }];

      //2.開始任務
      [operation start];
SWIFT
      //1.創(chuàng)建NSBlockOperation對象
      let operation = NSBlockOperation { () -> Void in
          println(NSThread.currentThread())
      }

      //2.開始任務
      operation.start()
之前說過這樣的任務,默認會在當前線程執(zhí)行。但是 `NSBlockOperation` 還有一個方法:`addExecutionBlock:` ,通過這個方法可以給 `Operation` 添加多個執(zhí)行 `Block`。這樣 `Operation` 中的任務 會**并發(fā)執(zhí)行**,它會 在**主線程**和**其它的多個線程** 執(zhí)行這些任務,注意下面的打印結(jié)果:
OBJECTIVE-C
          //1.創(chuàng)建NSBlockOperation對象
          NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
              NSLog(@"%@", [NSThread currentThread]);
          }];

          //添加多個Block
          for (NSInteger i = 0; i < 5; i++) {
              [operation addExecutionBlock:^{
                  NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
              }];
          }

          //2.開始任務
          [operation start];
SWIFT
            //1.創(chuàng)建NSBlockOperation對象
            let operation = NSBlockOperation { () -> Void in
                NSLog("%@", NSThread.currentThread())
            }

            //2.添加多個Block
            for i in 0..<5 {
                operation.addExecutionBlock { () -> Void in
                    NSLog("第%ld次 - %@", i, NSThread.currentThread())
                }
            }

            //2.開始任務
            operation.start()
打印輸出
2017-04-11 17:50:16.585 test[17527:4095467] 第2次 - <NSThread: 0x7ff5c9701910>{number = 1, name = main}

2017-04-11 17:50:16.585 test[17527:4095666] 第1次 - <NSThread: 0x7ff5c972caf0>{number = 4, name = (null)}

2017-04-11 17:50:16.585 test[17527:4095665] <NSThread: 0x7ff5c961b610>{number = 3, name = (null)}

2017-04-11 17:50:16.585 test[17527:4095662] 第0次 - <NSThread: 0x7ff5c948d310>{number = 2, name = (null)}

2017-04-11 17:50:16.586 test[17527:4095666] 第3次 - <NSThread: 0x7ff5c972caf0>{number = 4, name = (null)}

2017-04-11 17:50:16.586 test[17527:4095467] 第4次 - <NSThread: 0x7ff5c9701910>{number = 1, name = main}

NOTEaddExecutionBlock 方法必須在 start() 方法之前執(zhí)行,否則就會報錯:

‘*** -[NSBlockOperation addExecutionBlock:]: blocks cannot be added after the operation has started executing or finished'

  • 自定義Operation

    除了上面的兩種 Operation 以外,我們還可以自定義 Operation。自定義 Operation 需要繼承 NSOperation類,并實現(xiàn)其 main() 方法,因為在調(diào)用 start()方法的時候,內(nèi)部會調(diào)用main() 方法完成相關(guān)邏輯。所以如果以上的兩個類無法滿足你的欲望的時候,你就需要自定義了。你想要實現(xiàn)什么功能都可以寫在里面。除此之外,你還需要實現(xiàn) cancel() 在內(nèi)的各種方法。

創(chuàng)建隊列

看過上面的內(nèi)容就知道,我們可以調(diào)用一個 NSOperation 對象的 start() 方法來啟動這個任務,但是這樣做他們默認是 同步執(zhí)行 的。就算是 addExecutionBlock 方法,也會在 當前線程和其他線程 中執(zhí)行,也就是說還是會占用當前線程。這是就要用到隊列 NSOperationQueue 了。而且,按類型來說的話一共有兩種類型:主隊列其他隊列只要添加到隊列,會自動調(diào)用任務的 start()方法

  • 主隊列
    細心的同學就會發(fā)現(xiàn),每套多線程方案都會有一個主線程(當然啦,說的是iOS中,像 pthread 這種多系統(tǒng)的方案并沒有,因為 UI線程 理論需要每種操作系統(tǒng)自己定制)。這是一個特殊的線程,必須串行。所以添加到主隊列的任務都會一個接一個地排著隊在主線程處理。
//OBJECTIVE-C
NSOperationQueue *queue = [NSOperationQueue mainQueue];
//SWIFT
let queue = NSOperationQueue.mainQueue()
  • 其他隊列
    因為主隊列比較特殊,所以會單獨有一個類方法來獲得主隊列。那么通過初始化產(chǎn)生的隊列就是其他隊列了,因為只有這兩種隊列,除了主隊列,其他隊列就不需要名字了。

    注意:其他隊列的任務會在其他線程并行執(zhí)行。

OBJECTIVE-C

//1.創(chuàng)建一個其他隊列    
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

//2.創(chuàng)建NSBlockOperation對象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"%@", [NSThread currentThread]);
}];

//3.添加多個Block
for (NSInteger i = 0; i < 5; i++) {
    [operation addExecutionBlock:^{
        NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
    }];
}

//4.隊列添加任務
[queue addOperation:operation];

SWIFT

//1.創(chuàng)建其他隊列
let queue = NSOperationQueue()

//2.創(chuàng)建NSBlockOperation對象
let operation = NSBlockOperation { () -> Void in
    NSLog("%@", NSThread.currentThread())
}

//3.添加多個Block
for i in 0..<5 {
    operation.addExecutionBlock { () -> Void in
        NSLog("第%ld次 - %@", i, NSThread.currentThread())
    }
}

//4.隊列添加任務
queue.addOperation(operation)

打印輸出

2017-04-11 20:26:28.463 test[18622:4443534] <NSThread: 0x7fd022c3ac10>{number = 5, name = (null)}
2017-04-11 20:26:28.463 test[18622:4443536] 第2次 - <NSThread: 0x7fd022e36d50>{number = 2, name = (null)}
2017-04-11 20:26:28.463 test[18622:4443535] 第0次 - <NSThread: 0x7fd022f237f0>{number = 4, name = (null)}
2017-04-11 20:26:28.463 test[18622:4443533] 第1次 - <NSThread: 0x7fd022d372b0>{number = 3, name = (null)}
2017-04-11 20:26:28.463 test[18622:4443534] 第3次 - <NSThread: 0x7fd022c3ac10>{number = 5, name = (null)}
2017-04-11 20:26:28.463 test[18622:4443536] 第4次 - <NSThread: 0x7fd022e36d50>{number = 2, name = (null)}

OK, 這時應該發(fā)問了,大家將 NSOperationQueueGCD的隊列 相比較就會發(fā)現(xiàn),這里沒有串行隊列,那如果我想要10個任務在其他線程串行的執(zhí)行怎么辦?

這就是蘋果封裝的妙處,你不用管串行、并行、同步、異步這些名詞。NSOperationQueue 有一個參數(shù) maxConcurrentOperationCount 最大并發(fā)數(shù),用來設(shè)置最多可以讓多少個任務同時執(zhí)行。當你把它設(shè)置為 1的時候,他不就是串行了嘛!

NSOperationQueue 還有一個添加任務的方法,- (void)addOperationWithBlock:(void (^)(void))block;,這是不是和 GCD 差不多?這樣就可以添加一個任務到隊列中了,十分方便。

NSOperation 有一個非常實用的功能,那就是添加依賴。比如有 3 個任務:A: 從服務器上下載一張圖片,B:給這張圖片加個水印,C:把圖片返回給服務器。這時就可以用到依賴了:

OBJECTIVE-C

//1.任務一:下載圖片
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"下載圖片 - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

//2.任務二:打水印
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"打水印   - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

//3.任務三:上傳圖片
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"上傳圖片 - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

//4.設(shè)置依賴
[operation2 addDependency:operation1];      //任務二依賴任務一
[operation3 addDependency:operation2];      //任務三依賴任務二

//5.創(chuàng)建隊列并加入任務
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];

SWIFT

//1.任務一:下載圖片
let operation1 = NSBlockOperation { () -> Void in
    NSLog("下載圖片 - %@", NSThread.currentThread())
    NSThread.sleepForTimeInterval(1.0)
}

//2.任務二:打水印
let operation2 = NSBlockOperation { () -> Void in
    NSLog("打水印   - %@", NSThread.currentThread())
    NSThread.sleepForTimeInterval(1.0)
}

//3.任務三:上傳圖片
let operation3 = NSBlockOperation { () -> Void in
    NSLog("上傳圖片 - %@", NSThread.currentThread())
    NSThread.sleepForTimeInterval(1.0)
}

//4.設(shè)置依賴
operation2.addDependency(operation1)    //任務二依賴任務一
operation3.addDependency(operation2)    //任務三依賴任務二

//5.創(chuàng)建隊列并加入任務
let queue = NSOperationQueue()
queue.addOperations([operation3, operation2, operation1], waitUntilFinished: false)

打印結(jié)果

2017-04-11 21:24:28.622 test[19392:4637517] 下載圖片 - <NSThread: 0x7fc10ad4d970>{number = 2, name = (null)}
2017-04-11 21:24:29.622 test[19392:4637515] 打水印 - <NSThread: 0x7fc10af20ef0>{number = 3, name = (null)}
2017-04-11 21:24:30.627 test[19392:4637515] 上傳圖片 - <NSThread: 0x7fc10af20ef0>{number = 3, name = (null)}

注意:

  • 不能添加相互依賴,會死鎖,比如 A依賴B,B依賴A。
  • 可以使用 removeDependency 來解除依賴關(guān)系。
  • 可以在不同的隊列之間依賴,反正就是這個依賴是添加到任務身上的,和隊列沒關(guān)系。

其他方法

以上就是一些主要方法, 下面還有一些常用方法需要大家注意:

NSOperation

    BOOL executing; //判斷任務是否正在執(zhí)行

    BOOL finished; //判斷任務是否完成

    void (^completionBlock)(void); //用來設(shè)置完成后需要執(zhí)行的操作

    - (void)cancel; //取消任務

    - (void)waitUntilFinished; //阻塞當前線程直到此任務執(zhí)行完畢

NSOperationQueue

    NSUInteger operationCount; //獲取隊列的任務數(shù)

    - (void)cancelAllOperations; //取消隊列中所有的任務

    - (void)waitUntilAllOperationsAreFinished; //阻塞當前線程直到此隊列中的所有任務執(zhí)行完畢

    [queue setSuspended:YES]; // 暫停queue

    [queue setSuspended:NO]; // 繼續(xù)queue
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容