iOS多線程 NSOperation

系列文章:

多線程

多線程 pthread、NSThread

多線程 GCD

多線程 NSOperation

多線程運用

NSOperation和NSOperationQueue

NSOperation是蘋果封裝的一套多線程的東西旅东,不像GCD是純C語言的执虹,這個是OC的冠蒋。但相比較之下GCD會更快一些识脆,但本質(zhì)上NSOPeration是對GDC的封裝倔既。

NSOperation相對于GCD:

NSOperation擁有更多的函數(shù)可用

NSOperationQueue中呢蔫,可以建立各個NSOperation之間的依賴關系切心。

NSOperationQueue支持KVO「琅伲可以監(jiān)測operation是否正在執(zhí)行(isExecuted)昙衅、是否結(jié)束(isFinished),是否取消(isCanceld)

GCD 只支持FIFO 的隊列定鸟,而NSOperationQueue可以調(diào)整隊列的執(zhí)行順序

NSOperation剖析

NSOperation是個抽象類,并不具備封裝任務的能力著瓶,必須使用它的子類來封裝任務
使用NSOperation子類的方式有3種:

  • NSInvocationOperation
  • NSBlockOperation
  • 自定義子類繼承NSOperation联予,實現(xiàn)內(nèi)部相應的方法

創(chuàng)建一個 Operation 后,需要調(diào)用 start 方法來啟動任務,它會 默認在當前線程同步執(zhí)行 沸久。當然你也可以在中途取消一個任務季眷,只需要調(diào)用其 cancel 方法即可。

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

  1. 將要執(zhí)行的任務封裝到一個 NSOperation 對象中窑睁。
  2. 將NSOperation對象添加到一個 NSOperationQueue 對象中挺峡。
  3. 系統(tǒng)會自動將NSOperationQueue中的NSOperation取出來放到一條新線程中執(zhí)行

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

添加任務

  • 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 這種不是類型安全的敗類的箫津。蘋果如是說狭姨。 這里有相關解釋

注意

默認情況下,調(diào)用了start方法后并不會開一條新線程去執(zhí)行操作苏遥,而是 在當前線程同步執(zhí)行操作 饼拍。
只有將NSOperation放到一個NSOperationQueue中,才會異步執(zhí)行操作

  • NSBlockOperation
// 1.創(chuàng)建NSBlockOperation對象
+ (id)blockOperationWithBlock:(void(^)(void))block;
// 2.通過addExecutionBlock:方法添加更多的操作
- (void)addExecutionBlock:(void(^)(void))block;

只要NSBlockOperation封裝的操作數(shù) > 1田炭,就會異步執(zhí)行操作

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()

打印輸出

2015-07-28 17:50:16.585 test[17527:4095467] 第2次 - <NSThread: 0x7ff5c9701910>{number = 1, name = main}
2015-07-28 17:50:16.585 test[17527:4095666] 第1次 - <NSThread: 0x7ff5c972caf0>{number = 4, name = (null)}
2015-07-28 17:50:16.585 test[17527:4095665] <NSThread: 0x7ff5c961b610>{number = 3, name = (null)}
2015-07-28 17:50:16.585 test[17527:4095662] 第0次 - <NSThread: 0x7ff5c948d310>{number = 2, name = (null)}
2015-07-28 17:50:16.586 test[17527:4095666] 第3次 - <NSThread: 0x7ff5c972caf0>{number = 4, name = (null)}
2015-07-28 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'

NOTE:大家可能發(fā)現(xiàn)了一個問題,為什么我在 Swift 里打印輸出使用 NSLog() 而不是 println() 呢丧鸯?原因是使用 print() / println() 輸出的話蛤铜,它會簡單地使用 流(stream) 的概念,學過 C++ 的都知道丛肢。它會把需要輸出的每個字符一個一個的輸出到控制臺围肥。普通使用并沒有問題,可是當多線程同步輸出的時候問題就來了蜂怎,由于很多 println() 同時打印穆刻,就會導致控制臺上的字符混亂的堆在一起,而NSLog() 就沒有這個問題杠步。到底是什么樣子的呢氢伟?你可以把上面 NSLog() 改為 println() 榜轿,然后一試便知。 更多 NSLog() 與 println() 的區(qū)別看這里

  • 自定義Operation

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

注意

自己創(chuàng)建自動釋放池(因為如果是異步操作,無法訪問主線程的自動釋放池)

經(jīng)常通過-(BOOL)isCancelled方法檢測操作是否被取消掀泳,對取消做出響應雪隧。

創(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)生的隊列就是其他隊列了陕截,因為只有這兩種隊列远寸,除了主隊列,其他隊列就不需要名字了。

  1. NSOperation可以調(diào)用start方法來執(zhí)行任務贱鄙,但默認是同步執(zhí)行的
  2. 如果將NSOperation添加到NSOperationQueue(操作隊列)中劝贸,系統(tǒng)會自動異步執(zhí)行NSOperation中的操作
// 添加操作到NSOperationQueue中
- (void)addOperation:(NSOperation*)op;
- (void)addOperationWithBlock:(void(^)(void))block;

注意:其他隊列的任務會在其他線程并行執(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)

打印輸出

2015-07-28 20:26:28.463 test[18622:4443534] <NSThread: 0x7fd022c3ac10>{number = 5, name = (null)}
2015-07-28 20:26:28.463 test[18622:4443536] 第2次 - <NSThread: 0x7fd022e36d50>{number = 2, name = (null)}
2015-07-28 20:26:28.463 test[18622:4443535] 第0次 - <NSThread: 0x7fd022f237f0>{number = 4, name = (null)}
2015-07-28 20:26:28.463 test[18622:4443533] 第1次 - <NSThread: 0x7fd022d372b0>{number = 3, name = (null)}
2015-07-28 20:26:28.463 test[18622:4443534] 第3次 - <NSThread: 0x7fd022c3ac10>{number = 5, name = (null)}
2015-07-28 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ù),用來設置最多可以讓多少個任務同時執(zhí)行譬正。當你把它設置為 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.創(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.設置依賴
operation2.addDependency(operation1)    //任務二依賴任務一
operation3.addDependency(operation2)    //任務三依賴任務二

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

打印結(jié)果

2015-07-28 21:24:28.622 test[19392:4637517] 下載圖片 - <NSThread: 0x7fc10ad4d970>{number = 2, name = (null)}
2015-07-28 21:24:29.622 test[19392:4637515] 打水印   - <NSThread: 0x7fc10af20ef0>{number = 3, name = (null)}
2015-07-28 21:24:30.627 test[19392:4637515] 上傳圖片 - <NSThread: 0x7fc10af20ef0>{number = 3, name = (null)}
  • 注意:不能添加相互依賴客燕,會死鎖,比如 A依賴B俺驶,B依賴A幸逆。
  • 可以使用 removeDependency 來解除依賴關系。
  • 可以在不同的隊列之間依賴暮现,反正就是這個依賴是添加到任務身上的还绘,和隊列沒關系。

其他方法

  • NSOperation
BOOL executing;                   //判斷任務是否正在執(zhí)行
BOOL finished;                    //判斷任務是否完成
void (^completionBlock)(void);        //用來設置完成后需要執(zhí)行的操作
- (void)cancel;                  //取消任務
- (void)waitUntilFinished;           //阻塞當前線程直到此任務執(zhí)行完畢

// 依賴: NSOperation之間可以設置依賴來保證執(zhí)行順序
// 1.比如一定要讓操作A執(zhí)行完后栖袋,才能執(zhí)行操作B拍顷,可以這么寫
[operationB addDependency:operationA];
// 操作B依賴于操作A注意:可以在不同queue的NSOperation之間創(chuàng)建依賴關系

// 操作的監(jiān)聽
// 1.可以監(jiān)聽一個操作的執(zhí)行完畢
- (void(^)(void))completionBlock;
- (void)setCompletionBlock:(void(^)(void))block;
  • NSOperationQueue
NSUInteger operationCount;        //獲取隊列的任務數(shù)
- (void)waitUntilAllOperationsAreFinished;   //阻塞當前線程直到此隊列中的所有任務執(zhí)行完畢


// 最大并發(fā)數(shù): 可以通過對最大并發(fā)數(shù)設置,控制程序中線程的數(shù)量

// 1.最大并發(fā)數(shù)的相關方法
- (NSInteger)maxConcurrentOperationCount;
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;

// 取消塘幅、暫停昔案、恢復
// 1.取消隊列的所有操作
- (void)cancelAllOperations;
// 2.取消單個操作
- (void)cancel;
// 暫停queue
- (void)setSuspended:(BOOL)b;
// YES代表暫停隊列尿贫,NO代表繼續(xù)隊列
[queue setSuspended:YES];         // 暫停queue
[queue setSuspended:NO];          // 繼續(xù)queue

// 恢復隊列
- (BOOL)isSuspended;
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市踏揣,隨后出現(xiàn)的幾起案子庆亡,更是在濱河造成了極大的恐慌,老刑警劉巖捞稿,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件又谋,死亡現(xiàn)場離奇詭異,居然都是意外死亡娱局,警方通過查閱死者的電腦和手機彰亥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來衰齐,“玉大人任斋,你說我怎么就攤上這事〕芴危” “怎么了废酷?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長犬第。 經(jīng)常有香客問我锦积,道長,這世上最難降的妖魔是什么歉嗓? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任丰介,我火速辦了婚禮,結(jié)果婚禮上鉴分,老公的妹妹穿的比我還像新娘哮幢。我一直安慰自己,他們只是感情好志珍,可當我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布橙垢。 她就那樣靜靜地躺著,像睡著了一般伦糯。 火紅的嫁衣襯著肌膚如雪柜某。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天敛纲,我揣著相機與錄音喂击,去河邊找鬼。 笑死淤翔,一個胖子當著我的面吹牛翰绊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼监嗜,長吁一口氣:“原來是場噩夢啊……” “哼谐檀!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起裁奇,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤桐猬,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后框喳,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體课幕,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年五垮,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片杜秸。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡放仗,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出撬碟,到底是詐尸還是另有隱情诞挨,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布呢蛤,位于F島的核電站惶傻,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏其障。R本人自食惡果不足惜银室,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望励翼。 院中可真熱鬧蜈敢,春花似錦、人聲如沸汽抚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽造烁。三九已至否过,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間惭蟋,已是汗流浹背苗桂。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留敞葛,地道東北人誉察。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像惹谐,于是被迫代替她去往敵國和親持偏。 傳聞我的和親對象是個殘疾皇子驼卖,可洞房花燭夜當晚...
    茶點故事閱讀 44,601評論 2 353

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

  • 和GCD的對比 操作隊列,比GCD更早鸿秆,GCD在設計上很多都是基于操作隊列的原理構(gòu)建的酌畜。在iOS4之后,操作隊列的...
    doudo閱讀 183評論 0 0
  • 1. NSOperation簡介 在某些情況下卿叽,執(zhí)行后臺任務GCD不一定是最好的方式桥胞,還有一種技術叫做操作隊列NS...
    Claire_wu閱讀 1,043評論 0 0
  • 在這篇文章中,我將為你整理一下 iOS 開發(fā)中幾種多線程方案考婴,以及其使用方法和注意事項贩虾。當然也會給出幾種多線程的案...
    張戰(zhàn)威ican閱讀 603評論 0 0
  • 寫在前面 當然,花生米知道沥阱,在時間維度上缎罢,生活不在過去亦不在未來;在空間維度上考杉,生活不在遙遠的別處...
    花生米小木屋閱讀 540評論 0 0
  • 網(wǎng)易云的熱評里崇棠,總是有一大段一大段的關于我和她的故事咽袜,或者我和他的故事,說不上多矯情枕稀,也說不上多精彩询刹,但...
    十鯉閱讀 787評論 8 11