系列文章:
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ū)ο笞庸危允褂闷饋砀美斫狻?大家可以看到 NSOperation
和 NSOperationQueue
分別對應 GCD 的 任務
和 隊列
。操作步驟也很好理解:
- 將要執(zhí)行的任務封裝到一個 NSOperation 對象中窑睁。
- 將NSOperation對象添加到一個 NSOperationQueue 對象中挺峡。
- 系統(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}
NOTE:addExecutionBlock
方法必須在 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)生的隊列就是其他隊列了陕截,因為只有這兩種隊列远寸,除了主隊列,其他隊列就不需要名字了。
- NSOperation可以調(diào)用start方法來執(zhí)行任務贱鄙,但默認是同步執(zhí)行的
- 如果將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ā)問了,大家將 NSOperationQueue
與 GCD的隊列
相比較就會發(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;