和GCD的對比
操作隊列撑帖,比GCD更早筐眷,GCD在設(shè)計上很多都是基于操作隊列的原理構(gòu)建的铐炫。在iOS4之后垒手,操作隊列的底層都是由GCD來實現(xiàn)的。
GCD是純c的語法函數(shù)倒信,操作隊列是oc語法對象科贬。
操作隊列能提供比GCD更精細的控制,比如:
- 取消某個操作(開始之前可以取消鳖悠,已經(jīng)開始的任務(wù)無法取消)榜掌。
- 設(shè)置操作之間的依賴關(guān)系、設(shè)置操作的優(yōu)先級乘综。(GCD只能對queue設(shè)置優(yōu)先級)
- 狀態(tài)控制唐责。對單個NSOperation 狀態(tài)屬性isReady、isExecuting瘾带、 isCancelled鼠哥、isFinished通過kvo監(jiān)控,執(zhí)行完畢之后獲得通知看政。
一朴恳、NSOperation簡介
NSOperation是蘋果提供給我們的一套多線程解決方案。實際上NSOperation是基于GCD更高一層的封裝允蚣,但是比GCD更簡單易用于颖、代碼可讀性也更高。
NSOperation需要配合NSOperationQueue來實現(xiàn)多線程嚷兔。因為默認情況下森渐,NSOperation單獨使用時系統(tǒng)同步執(zhí)行操作,并沒有開辟新線程的能力冒晰,只有配合NSOperationQueue才能實現(xiàn)異步執(zhí)行同衣。
因為NSOperation是基于GCD的,那么使用起來也和GCD差不多壶运,其中耐齐,NSOperation相當(dāng)于GCD中的任務(wù),而NSOperationQueue則相當(dāng)于GCD中的隊列。NSOperation實現(xiàn)多線程的使用步驟分為三步:
- 創(chuàng)建任務(wù):先將需要執(zhí)行的操作封裝到一個NSOperation對象中埠况。
- 創(chuàng)建隊列:創(chuàng)建NSOperationQueue對象耸携。
- 將任務(wù)加入到隊列中:然后將NSOperation對象添加到NSOperationQueue中。
之后呢辕翰,系統(tǒng)就會自動將NSOperationQueue中的NSOperation取出來夺衍,在新線程中執(zhí)行操作。
下面我們來學(xué)習(xí)下NSOperation和NSOperationQueue的基本使用喜命。
二刷后、NSOperation和NSOperationQueue的基本使用
1. 創(chuàng)建任務(wù)
NSOperation是個抽象類,并不能封裝任務(wù)渊抄。我們只有使用它的子類來封裝任務(wù)。我們有三種方式來封裝任務(wù)丧裁。
- 使用子類NSInvocationOperation
- 使用子類NSBlockOperation
- 定義繼承自NSOperation的子類护桦,通過實現(xiàn)內(nèi)部相應(yīng)的方法來封裝任務(wù)。
在不使用NSOperationQueue煎娇,單獨使用NSOperation的情況下系統(tǒng)同步執(zhí)行操作二庵,下面我們學(xué)習(xí)以下任務(wù)的三種創(chuàng)建方式。
1. 使用子類- NSInvocationOperation:
// 1.創(chuàng)建NSInvocationOperation對象
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
// 2.調(diào)用start方法開始執(zhí)行操作
[op start];
- (void)run
{
NSLog(@"------%@", [NSThread currentThread]);
}
輸出結(jié)果:
2016-09-05 14:29:58.483 NSOperation[15834:2384555] ------<NSThread: 0x7fa3e2e05410>{number = 1, name = main}
從中可以看到缓呛,在沒有使用NSOperationQueue催享、單獨使用NSInvocationOperation的情況下,NSInvocationOperation在主線程執(zhí)行操作哟绊,并沒有開啟新線程因妙。
2. 使用子類- NSBlockOperation
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
// 在主線程
NSLog(@"------%@", [NSThread currentThread]);
}];
[op start];
輸出結(jié)果:
2016-09-05 14:33:15.268 NSOperation[15884:2387780] ------<NSThread: 0x7fb2196012c0>{number = 1, name = main}
我們同樣可以看到,在沒有使用NSOperationQueue票髓、單獨使用NSBlockOperation的情況下攀涵,NSBlockOperation也是在主線程執(zhí)行操作,并沒有開啟新線程洽沟。
但是以故,NSBlockOperation還提供了一個方法addExecutionBlock:,通過addExecutionBlock:就可以為NSBlockOperation添加額外的操作裆操,這些額外的操作就會在其他線程并發(fā)執(zhí)行怒详。
- (void)blockOperation
{
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
// 在主線程
NSLog(@"1------%@", [NSThread currentThread]);
}];
// 添加額外的任務(wù)(在子線程執(zhí)行)
[op addExecutionBlock:^{
NSLog(@"2------%@", [NSThread currentThread]);
}];
[op addExecutionBlock:^{
NSLog(@"3------%@", [NSThread currentThread]);
}];
[op addExecutionBlock:^{
NSLog(@"4------%@", [NSThread currentThread]);
}];
[op start];
}
輸出結(jié)果:
2016-09-05 14:36:59.353 NSOperation[15896:2390616] 1------<NSThread: 0x7ff633f03be0>{number = 1, name = main}
2016-09-05 14:36:59.354 NSOperation[15896:2390825] 2------<NSThread: 0x7ff633e24600>{number = 2, name = (null)}
2016-09-05 14:36:59.354 NSOperation[15896:2390657] 3------<NSThread: 0x7ff633c411e0>{number = 3, name = (null)}
2016-09-05 14:36:59.354 NSOperation[15896:2390656] 4------<NSThread: 0x7ff633f1d3e0>{number = 4, name = (null)}
可以看出,blockOperationWithBlock:方法中的操作是在主線程中執(zhí)行的踪区,而addExecutionBlock:方法中的操作是在其他線程中執(zhí)行的昆烁。
3. 定義繼承自NSOperation的子類
先定義一個繼承自NSOperation的子類,重寫main方法
YSCOperation.h
#import <Foundation/Foundation.h>
@interface YSCOperation : NSOperation
@end
YSCOperation.m
#import "YSCOperation.h"
@implementation YSCOperation
/**
* 需要執(zhí)行的任務(wù)
*/
- (void)main
{
for (int i = 0; i < 2; ++i) {
NSLog(@"1-----%@",[NSThread currentThread]);
}
}
@end
然后使用的時候?qū)腩^文件YSCOperation.h缎岗。
// 創(chuàng)建YSCOperation
YSCOperation *op1 = [[YSCOperation alloc] init];
[op1 start];
輸出結(jié)果:
2016-09-05 18:15:59.674 NSOperation[16566:2501606] 1-----<NSThread: 0x7f8030d05150>{number = 1, name = main}
2016-09-05 18:15:59.675 NSOperation[16566:2501606] 1-----<NSThread: 0x7f8030d05150>{number = 1, name = main}
可以看出:在沒有使用NSOperationQueue善玫、單獨使用自定義子類的情況下,是在主線程執(zhí)行操作,并沒有開啟新線程茅郎。
2. 創(chuàng)建隊列
和GCD中的并發(fā)隊列蜗元、串行隊列略有不同的是:NSOperationQueue一共有兩種隊列:主隊列、其他隊列系冗。其中其他隊列同時包含了串行奕扣、并發(fā)功能。下邊是主隊列掌敬、其他隊列的基本創(chuàng)建方法和特點惯豆。
- 主隊列
凡是添加到主隊列中的任務(wù)(NSOperation),都會放到主線程中執(zhí)行
NSOperationQueue *queue = [NSOperationQueue mainQueue]; - 其他隊列(非主隊列)
添加到這種隊列中的任務(wù)(NSOperation)奔害,就會自動放到子線程中執(zhí)行
同時包含了:串行楷兽、并發(fā)功能
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
3. 將任務(wù)加入到隊列中
前邊說了,NSOperation需要配合NSOperationQueue來實現(xiàn)多線程华临。
那么我們需要將創(chuàng)建好的任務(wù)加入到隊列中去芯杀。總共有兩種方法
- (void)addOperation:(NSOperation *)op;
需要先創(chuàng)建任務(wù)雅潭,再將創(chuàng)建好的任務(wù)加入到創(chuàng)建好的隊列中去
- (void)addOperationToQueue
{
// 1.創(chuàng)建隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2. 創(chuàng)建操作
// 創(chuàng)建NSInvocationOperation
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
// 創(chuàng)建NSBlockOperation
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; ++i) {
NSLog(@"1-----%@", [NSThread currentThread]);
}
}];
// 3. 添加操作到隊列中:addOperation:
[queue addOperation:op1]; // [op1 start]
[queue addOperation:op2]; // [op2 start]
}
- (void)run
{
for (int i = 0; i < 2; ++i) {
NSLog(@"2-----%@", [NSThread currentThread]);
}
}
輸出結(jié)果:
2016-09-05 17:06:00.241 NSOperationQueue[16201:2452281] 1-----<NSThread: 0x7fe4824080e0>{number = 3, name = (null)}
2016-09-05 17:06:00.241 NSOperationQueue[16201:2452175] 2-----<NSThread: 0x7fe482404a50>{number = 2, name = (null)}
2016-09-05 17:06:00.242 NSOperationQueue[16201:2452175] 2-----<NSThread: 0x7fe482404a50>{number = 2, name = (null)}
2016-09-05 17:06:00.241 NSOperationQueue[16201:2452281] 1-----<NSThread: 0x7fe4824080e0>{number = 3, name = (null)}
可以看出:NSInvocationOperation和NSOperationQueue結(jié)合后能夠開啟新線程归露。
- (void)addOperationWithBlock:(void (^)(void))block;
無需先創(chuàng)建任務(wù)奏甫,在block中添加任務(wù)锭部,直接將任務(wù)block加入到隊列中捏顺。
- (void)addOperationWithBlockToQueue
{
// 1. 創(chuàng)建隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2. 添加操作到隊列中:addOperationWithBlock:
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; ++i) {
NSLog(@"-----%@", [NSThread currentThread]);
}
}];
}
輸出結(jié)果:
2016-09-05 17:10:47.023 NSOperationQueue[16293:2457487] -----<NSThread: 0x7ffa6bc0e1e0>{number = 2, name = (null)}
2016-09-05 17:10:47.024 NSOperationQueue[16293:2457487] -----<NSThread: 0x7ffa6bc0e1e0>{number = 2, name = (null)}
可以看出addOperationWithBlock:和NSOperationQueue能夠開啟新線程,進行并發(fā)執(zhí)行椿浓。
三太援、 控制串行執(zhí)行和并行執(zhí)行的關(guān)鍵
之前我們說過,NSOperationQueue創(chuàng)建的其他隊列同時具有串行扳碍、并發(fā)功能粉寞,上邊我們演示了并發(fā)功能,那么他的串行功能是如何實現(xiàn)的左腔?
這里有個關(guān)鍵參數(shù)maxConcurrentOperationCount唧垦,叫做最大并發(fā)數(shù)。
最大并發(fā)數(shù):maxConcurrentOperationCount
- maxConcurrentOperationCount默認情況下為-1液样,表示不進行限制振亮,默認為并發(fā)執(zhí)行。
- 當(dāng)maxConcurrentOperationCount為1時鞭莽,進行串行執(zhí)行坊秸。
- 當(dāng)maxConcurrentOperationCount大于1時,進行并發(fā)執(zhí)行澎怒,當(dāng)然這個值不應(yīng)超過系統(tǒng)限制褒搔,即使自己設(shè)置一個很大的值,系統(tǒng)也會自動調(diào)整。
- (void)opetationQueue
{
// 創(chuàng)建隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 設(shè)置最大并發(fā)操作數(shù)
// queue.maxConcurrentOperationCount = 2;
queue.maxConcurrentOperationCount = 1; // 就變成了串行隊列
// 添加操作
[queue addOperationWithBlock:^{
NSLog(@"1-----%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:0.01];
}];
[queue addOperationWithBlock:^{
NSLog(@"2-----%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:0.01];
}];
[queue addOperationWithBlock:^{
NSLog(@"3-----%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:0.01];
}];
[queue addOperationWithBlock:^{
NSLog(@"4-----%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:0.01];
}];
[queue addOperationWithBlock:^{
NSLog(@"5-----%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:0.01];
}];
[queue addOperationWithBlock:^{
NSLog(@"6-----%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:0.01];
}];
}
最大并發(fā)數(shù)為1輸出結(jié)果:
2016-09-05 17:21:54.124 NSOperationQueue[16320:2464630] 1-----<NSThread: 0x7fc892d0b3a0>{number = 2, name = (null)}
2016-09-05 17:21:54.136 NSOperationQueue[16320:2464631] 2-----<NSThread: 0x7fc892c0a7b0>{number = 3, name = (null)}
2016-09-05 17:21:54.148 NSOperationQueue[16320:2464630] 3-----<NSThread: 0x7fc892d0b3a0>{number = 2, name = (null)}
2016-09-05 17:21:54.160 NSOperationQueue[16320:2464631] 4-----<NSThread: 0x7fc892c0a7b0>{number = 3, name = (null)}
2016-09-05 17:21:54.171 NSOperationQueue[16320:2464631] 5-----<NSThread: 0x7fc892c0a7b0>{number = 3, name = (null)}
2016-09-05 17:21:54.184 NSOperationQueue[16320:2464630] 6-----<NSThread: 0x7fc892d0b3a0>{number = 2, name = (null)}
最大并發(fā)數(shù)為2輸出結(jié)果:
2016-09-05 17:23:36.030 NSOperationQueue[16331:2466366] 2-----<NSThread: 0x7fd729f0f270>{number = 3, name = (null)}
2016-09-05 17:23:36.030 NSOperationQueue[16331:2466491] 1-----<NSThread: 0x7fd729f4e290>{number = 2, name = (null)}
2016-09-05 17:23:36.041 NSOperationQueue[16331:2466367] 3-----<NSThread: 0x7fd729d214e0>{number = 4, name = (null)}
2016-09-05 17:23:36.041 NSOperationQueue[16331:2466366] 4-----<NSThread: 0x7fd729f0f270>{number = 3, name = (null)}
2016-09-05 17:23:36.053 NSOperationQueue[16331:2466366] 6-----<NSThread: 0x7fd729f0f270>{number = 3, name = (null)}
2016-09-05 17:23:36.053 NSOperationQueue[16331:2466511] 5-----<NSThread: 0x7fd729e056c0>{number = 5, name = (null)}
可以看出:當(dāng)最大并發(fā)數(shù)為1時星瘾,任務(wù)是按順序串行執(zhí)行的走孽。當(dāng)最大并發(fā)數(shù)為2時,任務(wù)是并發(fā)執(zhí)行的琳状。而且開啟線程數(shù)量是由系統(tǒng)決定的磕瓷,不需要我們來管理。這樣看來念逞,是不是比GCD還要簡單了許多困食?
四、 操作依賴
NSOperation和NSOperationQueue最吸引人的地方是它能添加操作之間的依賴關(guān)系翎承。比如說有A硕盹、B兩個操作,其中A執(zhí)行完操作叨咖,B才能執(zhí)行操作瘩例,那么就需要讓B依賴于A。具體如下:
- (void)addDependency
{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1-----%@", [NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2-----%@", [NSThread currentThread]);
}];
[op2 addDependency:op1]; // 讓op2 依賴于 op1芒澜,則先執(zhí)行op1,在執(zhí)行op2
[queue addOperation:op1];
[queue addOperation:op2];
}
輸出結(jié)果:
2016-09-05 17:51:28.811 操作依賴[16423:2484866] 1-----<NSThread: 0x7fc138e1e7c0>{number = 2, name = (null)}
2016-09-05 17:51:28.812 操作依賴[16423:2484866] 2-----<NSThread: 0x7fc138e1e7c0>{number = 2, name = (null)}
可以看到创淡,無論運行幾次痴晦,其結(jié)果都是op1先執(zhí)行,op2后執(zhí)行琳彩。
五誊酌、 一些其他方法
- -(void)cancel; NSOperation提供的方法,可取消單個操作
- -(void)cancelAllOperations; NSOperationQueue提供的方法露乏,可以取消隊列的所有操作
- -(void)setSuspended:(BOOL)b; 可設(shè)置任務(wù)的暫停和恢復(fù)碧浊,YES代表暫停隊列,NO代表恢復(fù)隊列
- -(BOOL)isSuspended; 判斷暫停狀態(tài)
注意:
這里的暫停和取消并不代表可以將當(dāng)前的操作立即取消瘟仿,而是當(dāng)當(dāng)前的操作執(zhí)行完畢之后不再執(zhí)行新的操作箱锐。
暫停和取消的區(qū)別就在于:暫停操作之后還可以恢復(fù)操作,繼續(xù)向下執(zhí)行劳较;而取消操作之后驹止,所有的操作就清空了,無法再接著執(zhí)行剩下的操作观蜗。
六臊恋、狀態(tài)控制
我們可以自行控制NSOperation的狀態(tài):
- 重寫start方法,我們需要自行控制狀態(tài)墓捻。
參考: