NSOperation 是蘋果公司對 GCD 的封裝,完全面向對象纪铺,所以使用起來更好理解。
NSOperation 和 NSOperationQueue 分別對應 GCD 的 任務 和 隊列 。
操作步驟:
1.將要執(zhí)行的任務封裝到一個 NSOperation 對象中。
2.將此任務添加到一個 NSOperationQueue 對象中萌踱。
3.然后系統(tǒng)就會自動在執(zhí)行任務。
添加任務
NSOperation 只是一個抽象類限书,所以不能封裝任務虫蝶。
但它有 2 個子類用于封裝任務。NSInvocationOperation 和 NSBlockOperation 倦西。
創(chuàng)建一個 Operation 后能真,需要調用 start 方法來啟動任務,它會 默認在當前隊列同步執(zhí)行扰柠。當然你也可以在中途取消一個任務粉铐,只需要調用其 cancel 方法即可。
NSInvocationOperation
示例代碼:
-(void)test
{
//1.創(chuàng)建NSInvocationOperation對象
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
//2.開始執(zhí)行
[operation start];
}
-(void)run
{
NSLog(@"run at %@",[NSThread currentThread]);
}
運行結果:
[19275:3197092] run at <NSThread: 0x60400007cc80>{number = 1, name = main}
NSBlockOperation
示例代碼:
-(void)test
{
//1.創(chuàng)建NSBlockOperation對象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
//2.開始任務
[operation start];
}
執(zhí)行結果:
[19319:3204356] <NSThread: 0x60400006fec0>{number = 1, name = main}
這樣的任務卤档,默認會在當前線程執(zhí)行蝙泼。但是 NSBlockOperation 還有一個方法:addExecutionBlock: ,通過這個方法可以給 Operation 添加多個執(zhí)行 Block劝枣。這樣 Operation 中的任務 會并發(fā)執(zhí)行汤踏,它會 在主線程和其它的多個線程 執(zhí)行這些任務。
-(void)test2
{
//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];
//NOTE:addExecutionBlock 方法必須在 start() 方法之前執(zhí)行舔腾,否則就會報錯:
// -[NSBlockOperation addExecutionBlock:]: blocks cannot be added
//after the operation has started executing or finished'
}
運行結果:
[19340:3206687] 第0次:<NSThread: 0x60000046d440>{number = 4, name = (null)}
[19340:3206689] <NSThread: 0x604000263740>{number = 3, name = (null)}
[19340:3206686] 第1次:<NSThread: 0x604000263780>{number = 5, name = (null)}
[19340:3206601] 第2次:<NSThread: 0x6000000725c0>{number = 1, name = main}
[19340:3206687] 第3次:<NSThread: 0x60000046d440>{number = 4, name = (null)}
[19340:3206689] 第4次:<NSThread: 0x604000263740>{number = 3, name = (null)}
自定義Operation
除了上面的兩種 Operation 以外溪胶,我們還可以自定義 Operation。自定義 Operation 需要繼承 NSOperation 類稳诚,并實現(xiàn)其 main() 方法哗脖,因為在調用 start() 方法的時候,內(nèi)部會調用 main() 方法完成相關邏輯。所以如果以上的兩個類無法滿足你的需求的時候才避,你就需要自定義了橱夭。你想要實現(xiàn)什么功能都可以寫在里面。除此之外桑逝,你還需要實現(xiàn) cancel() 在內(nèi)的各種方法棘劣。
NSOperationQueue
調用一個 NSOperation 對象的 start() 方法來啟動這個任務,但是這樣默認是 同步執(zhí)行 的肢娘。就算是 addExecutionBlock 方法呈础,也會在 當前線程和其他線程 中執(zhí)行,也就是說還是會占用當前線程橱健。這是就要用到隊列 NSOperationQueue 了而钞。
按類型來說的話一共有兩種類型:主隊列、其他隊列拘荡。
只要添加到隊列臼节,會自動調用任務的 start() 方法
主隊列
多線程方案都會有一個主線程(iOS中,像 pthread 這種多系統(tǒng)的方案并沒有珊皿,因為 UI線程 理論需要每種操作系統(tǒng)自己定制)瞭恰。這是一個特殊的線程袱巨,必須串行鸵隧。所以添加到主隊列的任務都會一個接一個地排著隊在主線程處理鹃两。
NSOperationQueue *queue = [NSOperationQueue mainQueue];
其他隊列
因為主隊列比較特殊,所以會單獨有一個類方法來獲得主隊列驶兜。那么通過初始化產(chǎn)生的隊列就是其他隊列了扼仲。
注意:其他隊列的任務會在其他線程并行執(zhí)行。
//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];
執(zhí)行結果:
[19376:3220751] 第1次:<NSThread: 0x604000263180>{number = 5, name = (null)}
[19376:3220745] 第0次:<NSThread: 0x604000262f00>{number = 4, name = (null)}
[19376:3220744] <NSThread: 0x600000468a80>{number = 3, name = (null)}
[19376:3220743] 第2次:<NSThread: 0x6040002631c0>{number = 6, name = (null)}
[19376:3220745] 第3次:<NSThread: 0x604000262f00>{number = 4, name = (null)}
[19376:3220751] 第4次:<NSThread: 0x604000263180>{number = 5, name = (null)}
將 NSOperationQueue 與 GCD的隊列 相比較就會發(fā)現(xiàn)抄淑,這里沒有并行隊列屠凶,那如果我想要10個任務在其他線程串行的執(zhí)行怎么辦?
不用管串行肆资、并行矗愧、同步、異步這些名詞郑原。NSOperationQueue 有一個參數(shù) maxConcurrentOperationCount 最大并發(fā)數(shù)唉韭,用來設置最多可以讓多少個任務同時執(zhí)行。當你把它設置為 1 的時候犯犁,就是串行纽哥。
NSOperationQueue 還有一個添加任務的方法,- (void)addOperationWithBlock:(void (^)(void))block;栖秕,這樣就可以添加一個任務到隊列中了,十分方便晓避。
NSOperation 有一個非常實用的功能簇捍,那就是添加依賴只壳。比如有 3 個任務:A: 從服務器上下載一張圖片,B:給這張圖片加個水印暑塑,C:把圖片返回給服務器吼句。這時就可以用到依賴了:
-(void)test7
{
//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];
}
運行結果:
2017-11-13 16:57:38.558598+0800 NSOperationStudy[19735:3299963] 下載圖片 - <NSThread: 0x60400007b800>{number = 3, name = (null)}
2017-11-13 16:57:39.563069+0800 NSOperationStudy[19735:3299958] 打水印 - <NSThread: 0x60000027ef00>{number = 4, name = (null)}
2017-11-13 16:57:40.567363+0800 NSOperationStudy[19735:3299963] 上傳圖片 - <NSThread: 0x60400007b800>{number = 3, name = (null)}
注意:不能添加相互依賴,會死鎖事格,比如 A依賴B惕艳,B依賴A。
可以使用 removeDependency 來解除依賴關系驹愚。
可以在不同的隊列之間依賴远搪,依賴是添加到任務身上的,和隊列沒關系逢捺。