NSOperation和NSOperationQueue

NSOperation和NSOperationQueue

NSOperation和NSOperationQueue
NSOperation 是 GCD 的封裝,是完全的物件導向類別,所以使用起來更簡單。 大家可以看到 NSOperation 和 NSOperationQueue 分别對照 GCD 的 Task 和 Queue 。

將要執行的任務封装到一個 NSOperation 物件中。
將此task添加到 NSOperationQueue 物件中。
然後系統就會自動執行任務。至於是同步還是異步、串行還是並行繼續往下看:

加入Task

NSOperation 只是一個抽象類別。
抽象類別是不能產生實體的類別,所以不能封裝task。
但有 2 個子類別是可以用封裝任務。

分别是:NSInvocationOperation 和 NSBlockOperation 。

建立一個 Operation 後,需要使用 start 方法啟用task,他會在當前的序列進行同步執行。而且也可以取消任務,只需要使用 cancel 方法即可。

NSInvocationOperation : 需要傳入一個方法。

OBJECTIVE-C
  //1.建立NSInvocationOperation物件
  NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];

  //2.開始執行
  [operation start];

  //SWIFT
在 Swift ,蘋果認為 NSInvocationOperation 這種不是安全的類別。

As of Xcode 6.1, NSInvocation is disabled in Swift, therefore, NSInvocationOperation is disabled too. See this thread in Developer Forum

Because it’s not type-safe or ARC-safe. Even in Objective-C it’s very very easy to shoot yourself in the foot trying to use it, especially under ARC. Use closures/blocks instead.
You have to use NSBlockOperation in Swift.

or addOperationWithBlock to NSOperationQueue

queue.addOperationWithBlock { [weak self] in
    self?.backgroundRun(self)
    return
}

NSBlockOperation

//OBJECTIVE-C
  //1.建立NSBlockOperation物件
  NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"%@", [NSThread currentThread]);
  }];

  //2.開始進行
  [operation start];

//SWIFT
  //1.建立NSBlockOperation物件
  let operation = NSBlockOperation { () -> Void in
      println(NSThread.currentThread())
  }

  //2.開始進行
  operation.start()

默認在當前的執行緒進行。但是 NSBlockOperation 有另外一個方法:addExecutionBlock: ,這個方法可以給 Operation 多加幾個Block。這樣 Operation 中的任務會平行執行,他會在主執行緒和其它的多個執行緒執行这些Task,注意下面结果:

      //OBJECTIVE-C
      //1.建立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.建立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()
打印输出
2016-08-03 17:50:16.585 test[17527:4095467] 第2次 - <NSThread: 0x7ff5c9701910>{number = 1, name = main}

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

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

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

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

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

!!NOTE

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

!!NOTE

為什麼在 Swift 使用 NSLog() 而不是 print() ?
原因是使用 print()輸出的话,他會簡單地使用 (stream) 的概念。
他會一次雜亂的輸出結果,而無法按照順序輸出。

自定義Operation

除了上面的兩種 Operation 外,也可以自定義 Operation。
自定義 Operation 需要繼承 NSOperation 類別,並實現其 main() 方法,因為在使用 start() 方法的时候,内部會使用 main() 方法完成相關邏輯。

所以如果以上的兩個類別無法滿足的时候,就需要自定義了。
想要實現什麼都可以寫在裡面。
除此之外,還需要實現 cancel() 在内的各種方法。

創建queue

看過上面的内容就知道,我们可以使用一个 NSOperation 物件的 start() 方法来啟動任務,但是這樣做預設是 同步 的。就算是 addExecutionBlock 方法,也會在當前執行緒和其他執行緒執行,也就是說還是會佔用現在的執行緒。這是就要用到序列 NSOperationQueue 了。而且,按類型来说的话一共有兩種類型類型:主序列、其他序列。只要添加到序列,會自動使用任務的 start() 方法

主執行緒

每套多執行緒方案一定有主執行緒(在iOS中,像 pthread 多系統的的方案並没有,因為UI執行緒理論需要每種作業系統自己定義)。這是一個特殊的執行須,所以並須串行。所以添加到主序列的任务都會一個一個排在主執行緒執行。

//OBJECTIVE-C
NSOperationQueue *queue = [NSOperationQueue mainQueue];

//SWIFT
let queue = NSOperationQueue.mainQueue()

其他序列

因为主序列比较特殊,所以單獨有一個方法通過主序列。那麼通過初始化產生的序列就是其他序列了,因为只有這兩種序列,除了主序列,其他序列就不需要名字了。

注意:其他序列會在其他執行緒同步執行。

OBJECTIVE-C
//1.建立一個其他的序列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

//2.建立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.創建其他序列
let queue = NSOperationQueue()

//2.建立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)

印出
2016-08-03 20:26:28.463 test[18622:4443534] <NSThread: 0x7fd022c3ac10>{number = 5, name = (null)}

2016-08-03 20:26:28.463 test[18622:4443536] 第2次 - <NSThread: 0x7fd022e36d50>{number = 2, name = (null)}

2016-08-03 20:26:28.463 test[18622:4443535] 第0次 - <NSThread: 0x7fd022f237f0>{number = 4, name = (null)}

2016-08-03 20:26:28.463 test[18622:4443533] 第1次 - <NSThread: 0x7fd022d372b0>{number = 3, name = (null)}

2016-08-03 20:26:28.463 test[18622:4443534] 第3次 - <NSThread: 0x7fd022c3ac10>{number = 5, name = (null)}

2016-08-03 20:26:28.463 test[18622:4443536] 第4次 - <NSThread: 0x7fd022e36d50>{number = 2, name = (null)}

將 NSOperationQueue 與 GCD的序列 比較就會發現,這裡沒有串行序列,那如果我想要十個任務在其他序列串行怎麼使用?

蘋果封裝的奧妙就在這裡,完全不用管執行緒的概念。

NSOperationQueue 有一個參數 maxConcurrentOperationCount 最大平行數,用來設定最多有多少任務可以執行。當你把它設為 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.設置添加依賴
[operation2 addDependency:operation1];      //任務二依賴任務一
[operation3 addDependency:operation2];      //任務三依賴任務二

//5.創建序列並加入任務
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.設置依賴添加
operation2.addDependency(operation1)    //任務二依賴任務一
operation3.addDependency(operation2)    //任務三依賴任務二

//5.創建序列並且加入任務
let queue = NSOperationQueue()
queue.addOperations([operation3, operation2, operation1], waitUntilFinished: false)

结果
2016-08-03 21:24:28.622 test[19392:4637517] 下載圖片 - <NSThread: 0x7fc10ad4d970>{number = 2, name = (null)}

2016-08-03 21:24:29.622 test[19392:4637515] 添加浮水印 - <NSThread: 0x7fc10af20ef0>{number = 3, name = (null)}

2016-08-03 21:24:30.627 test[19392:4637515] 上傳圖片 - <NSThread: 0x7fc10af20ef0>{number = 3, name = (null)}

注意:不能添加相互依賴,會導致死結,比如 A依賴B,B依賴A。
可以使用 removeDependency 来解除依賴關係。
可以在不同序列列之间依赖,反正就是這個依赖是添加到任務身上的,和序列没關係。


>執行緒同步
所謂執行緒同步就是為了防止多个執行緒搶奪同一個資源造成的資訊安全問題,所採取的一種措施。

有多種方法

>互斥锁 :给需要同步的程式碼加一個互斥鎖,就可以保證每次只有一個執行緒訪問

//OBJECTIVE-C
@synchronized(self) {
//需要執行的程式碼
}

//SWIFT
objc_sync_enter(self)
//需要執行的程式碼
objc_sync_exit(self)


>同步執行  :使用多執行緒的知識,把多個執行緒都要執行此段程式碼加到同一個串型的序列,這樣就實現了執行緒同步的概念。
>可以使用 GCD 和 NSOperation 兩種方案,我都寫出来。
OBJECTIVE-C
  //GCD
  //需要一個全域變數queue,讓所有的執行緒都入這個操作到這個queue
  dispatch_sync(queue, ^{
      NSInteger ticket = lastTicket;
      [NSThread sleepForTimeInterval:0.1];
      NSLog(@"%ld - %@",ticket, [NSThread currentThread]);
      ticket -= 1;
      lastTicket = ticket;
  });
  //NSOperation & NSOperationQueue
  //關鍵:  1. 全域的 NSOperationQueue, 所有的操作加到到同一個queue中
  //       2. 設定 queue 的 maxConcurrentOperationCount 為 1
  //       3. 如果後需操作需要Block中的结果,就需要使用每個操作的waitUntilFinished,阻礙當前執行緒,一直等到當前操作完成,才允許執行後面的。waitUntilFinished 要在添加到序列後面!


  NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
      NSInteger ticket = lastTicket;
      [NSThread sleepForTimeInterval:1];
      NSLog(@"%ld - %@",ticket, [NSThread currentThread]);
      ticket -= 1;
      lastTicket = ticket;
  }];

  [queue addOperation:operation];

  [operation waitUntilFinished];

  //後續要做的事情

SWIFT
改寫

延後操作
所谓延後操作就是延後一段時間再執行某段程式碼。下面说一些常用方法。

perform

OBJECTIVE-C
// 三秒後自動使用self的run方法:並且傳遞參數@”abc”
[self performSelector:@selector(run:) withObject:@”abc” afterDelay:3];

SWIFT
Swift 去掉這個方法了。

GCD

可以使用 GCD 中的 dispatch_after 方法,OC 和 Swift 都可以使用,這裡OC 的,Swift 的是一样的。

OBJECTIVE-C
// 創建序列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 設置延遲,單位秒
double delay = 3;

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), queue, ^{
// 三秒後需要執行的任務
});

NSTimer

NSTimer 是iOS中的一個計時器,除了延遲執行還有很多用法,不過這裡只說延後執行的用法。“`

OBJECTIVE-C
[NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(run:) userInfo:@"abc" repeats:NO];

Comments

Popular posts from this blog

MEGA 暫存檔案刪除

IOS GCD多執行緒

利用CMD指令強制刪除