NSOperation和NSOperationQueue
NSOperation
是蘋果公司對 GCD
的封裝,完全面向?qū)ο螅允褂闷饋砀美斫狻?大家可以看到 NSOperation
和 NSOperationQueue
分別對應 GCD
的 任務 和 隊列 。操作步驟也很好理解:
- 將要執(zhí)行的任務封裝到一個
NSOperation
對象中。 - 將此任務添加到一個
NSOperationQueue
對象中。
然后系統(tǒng)就會自動在執(zhí)行任務。至于同步還是異步、串行還是并行請繼續(xù)往下看:
添加任務
值得說明的是,NSOperation
只是一個抽象類,所以不能封裝任務。但它有 2 個子類用于封裝任務。分別是:NSInvocationOperation
和 NSBlockOperation
。創(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}
NOTE:addExecutionBlock
方法必須在 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ā)問了,大家將 NSOperationQueue
與 GCD
的隊列 相比較就會發(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