NSOperation類是iOS2.0推出的拼坎,從OS X10.6和iOS4推出GCD時泰鸡,又重寫了NSOperation和NSOperationQueue,NSOperation和NSOperationQueue分別對應GCD的任務和隊列余舶,NSOperation赠制、NSOperationQueue 是基于 GCD 更高一層的封裝,完全面向對象褥符。比 GCD 更加靈活喷楣,功能也更加強大。
1. Operation Queues(NSOperation铣焊,NSOperationQueue) vs. Grand Central Dispatch (GCD)
Operation Queues :相對 GCD 來說,使用 Operation Queues 會增加一點點額外的開銷叽讳,但是我們卻換來了非常強大的靈活性和功能,我們可以給 operation 之間添加依賴關系岛蚤、取消一個正在執(zhí)行的 operation 、暫停和恢復 operation queue 等懈糯;
GCD :則是一種更輕量級的涤妒,以 FIFO 的順序執(zhí)行并發(fā)任務的方式赚哗,使用 GCD 時我們并不關心任務的調度情況贿讹,而讓系統(tǒng)幫我們自動處理。但是 GCD 的短板也是非常明顯的,比如我們想要給任務之間添加依賴關系雹食、取消或者暫停一個正在執(zhí)行的任務時就會變得非常棘手畜普;
2. NSOperation,NSOperationQueue
NSOperation就是對任務進行的封裝群叶。它本身是一個抽象類吃挑,不能直接實例化。如果我們想要使用它來執(zhí)行具體任務街立,就必須創(chuàng)建自己的子類(繼承NSOperation)或者直接使用系統(tǒng)提供的兩個子類:NSInvocationOperation 和 NSBlockOperation舶衬;
NSOperationQueue就是任務的執(zhí)行隊列,可進行串行隊列的執(zhí)行或并發(fā)隊列的執(zhí)行赎离;
3. NSOperation開啟的兩種方式
第一種:直接由NSOperation子類對象啟動逛犹。 首先將需要執(zhí)行的操作封裝到NSOperation子類對象中,然后該對象調用start方法梁剔。直接使用start方法虽画,那么操作會在當前線程中同步執(zhí)行,不會創(chuàng)建新線程荣病。
第二種:NSOperation對象添加到NSOperationQueue對象中码撰,由該隊列對象啟動操作,操作會異步執(zhí)行:
(1)創(chuàng)建操作:先將需要執(zhí)行的操作封裝到一個 NSOperation 對象中个盆;
(2)創(chuàng)建隊列:創(chuàng)建 NSOperationQueue 對象脖岛;
(3)將操作加入到隊列中:將 NSOperation 對象添加到 NSOperationQueue 對象中;
系統(tǒng)自動從隊列中取出線程砾省,并且自動放到線程中執(zhí)行鸡岗。
4. NSOperation和NSOperationQueue的基本使用
4.1 NSInvocationOperation簡單使用
- 不通過NSOperationQueue
//NSInvocationOperation 同步執(zhí)行
- (void)invocationOperationSync
{
//創(chuàng)建 operation
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task:) object:@"任務1"];
[operation start];
}
- (void)task:(NSString *)taskName {
for (NSInteger i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"%@---%@", taskName, [NSThread currentThread]);
}
}
打印結果:
2019-07-20 20:18:15.101039+0800 NSOperationDemo[4388:112069] 任務1---<NSThread: 0x6000026d9380>{number = 1, name = main}
2019-07-20 20:18:16.102574+0800 NSOperationDemo[4388:112069] 任務1---<NSThread: 0x6000026d9380>{number = 1, name = main}
在沒有使用 NSOperationQueue,在主線程中單獨使用使用子類 NSInvocationOperation 執(zhí)行一個操作的情況下编兄,操作是在當前線程執(zhí)行的轩性,并沒有開啟新線程。
- 通過NSOperationQueue
//NSInvocationOperation 異步執(zhí)行
- (void)invocationOperationAsync
{
//創(chuàng)建 operation
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task:) object:@"任務1"];
//創(chuàng)建一個操作隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//將任務添加到隊列中
[queue addOperation:operation];
}
打印結果:
2019-07-20 20:52:31.394786+0800 NSOperationDemo[4838:122958] 任務1---<NSThread: 0x60000321dcc0>{number = 3, name = (null)}
2019-07-20 20:52:32.396118+0800 NSOperationDemo[4838:122958] 任務1---<NSThread: 0x60000321dcc0>{number = 3, name = (null)}
使用 NSOperationQueue狠鸳,操作會異步并發(fā)執(zhí)行揣苏,會開辟新線程。
4.2 NSBlockOperation簡單使用
- 不通過NSOperationQueue
//NSBlockOperation 同步執(zhí)行
- (void)blockOperationSync
{
//創(chuàng)建 operation
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
for (NSInteger i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"block operation---%@", [NSThread currentThread]);
}
}];
[operation start];
}
打印結果:
2019-07-20 21:48:56.673690+0800 NSOperationDemo[5561:142065] block operation---<NSThread: 0x60000018e980>{number = 1, name = main}
2019-07-20 21:48:57.675189+0800 NSOperationDemo[5561:142065] block operation---<NSThread: 0x60000018e980>{number = 1, name = main}
同樣在沒有使用 NSOperationQueue件舵,不會開啟新的線程卸察。
- 通過NSOperationQueue
//NSBlockOperation 異步執(zhí)行
- (void)blockOperationAsync
{
//創(chuàng)建 operation
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
for (NSInteger i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"block operation---%@", [NSThread currentThread]);
}
}];
//創(chuàng)建一個操作隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//將任務添加到隊列中
[queue addOperation:operation];
}
打印結果:
2019-07-20 21:54:58.094872+0800 NSOperationDemo[5650:144283] block operation---<NSThread: 0x600002ab1540>{number = 3, name = (null)}
2019-07-20 21:54:59.099257+0800 NSOperationDemo[5650:144283] block operation---<NSThread: 0x600002ab1540>{number = 3, name = (null)}
使用 NSOperationQueue,操作會異步并發(fā)執(zhí)行铅祸,會開辟新線程坑质。
另外合武,NSBlockOperation 還提供了一個方法。
- (void)addExecutionBlock:(void (^)(void))block;
這個方法可以為 NSBlockOperation 添加額外的操作涡扼。每個操作可以異步并發(fā)執(zhí)行稼跳,所有相關的操作執(zhí)行完成時,整個操作才算完成吃沪。
簡單示例:
//NSBlockOperation addExecutionBlock
- (void)blockOperationAddExecutionBlock
{
//創(chuàng)建 operation
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
for (NSInteger i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"任務1:---%@", [NSThread currentThread]);
}
}];
for (NSInteger i = 2; i < 7; ++i) {
[operation addExecutionBlock:^{
for (NSInteger j = 0; j < 2; ++j) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"任務%ld:---%@", i, [NSThread currentThread]);
}
}];
}
[operation start];
}
打印結果:
2019-07-22 10:43:51.309602+0800 NSOperationDemo[2028:55710] 任務4:---<NSThread: 0x600000e1c180>{number = 1, name = main}
2019-07-22 10:43:51.309602+0800 NSOperationDemo[2028:55757] 任務1:---<NSThread: 0x600000e4b680>{number = 3, name = (null)}
2019-07-22 10:43:51.309602+0800 NSOperationDemo[2028:55755] 任務2:---<NSThread: 0x600000e748c0>{number = 5, name = (null)}
2019-07-22 10:43:51.309611+0800 NSOperationDemo[2028:55758] 任務3:---<NSThread: 0x600000e71400>{number = 4, name = (null)}
2019-07-22 10:43:52.310394+0800 NSOperationDemo[2028:55757] 任務1:---<NSThread: 0x600000e4b680>{number = 3, name = (null)}
2019-07-22 10:43:52.310441+0800 NSOperationDemo[2028:55758] 任務3:---<NSThread: 0x600000e71400>{number = 4, name = (null)}
2019-07-22 10:43:52.310392+0800 NSOperationDemo[2028:55710] 任務4:---<NSThread: 0x600000e1c180>{number = 1, name = main}
2019-07-22 10:43:52.310452+0800 NSOperationDemo[2028:55755] 任務2:---<NSThread: 0x600000e748c0>{number = 5, name = (null)}
2019-07-22 10:43:53.311787+0800 NSOperationDemo[2028:55757] 任務5:---<NSThread: 0x600000e4b680>{number = 3, name = (null)}
2019-07-22 10:43:53.311787+0800 NSOperationDemo[2028:55710] 任務6:---<NSThread: 0x600000e1c180>{number = 1, name = main}
2019-07-22 10:43:54.313078+0800 NSOperationDemo[2028:55710] 任務6:---<NSThread: 0x600000e1c180>{number = 1, name = main}
2019-07-22 10:43:54.313078+0800 NSOperationDemo[2028:55757] 任務5:---<NSThread: 0x600000e4b680>{number = 3, name = (null)}
從打印結果可以看出汤善,任務1的操作沒有在主線程執(zhí)行,任務4的操作在主線程執(zhí)行票彪,得出:
添加的操作多的話红淡,blockOperationWithBlock: 中的操作也可能會在其他線程(非當前線程)中執(zhí)行,這是由系統(tǒng)決定的降铸,并不是說添加到 blockOperationWithBlock: 中的操作一定會在當前線程中執(zhí)行在旱。同樣addExecutionBlock: 也是一樣,在哪個線程執(zhí)行是系統(tǒng)決定垮耳。
4.3 自定義NSOperation
當系統(tǒng)預定義的兩個子類 NSInvocationOperation 和 NSBlockOperation 不能很好的滿足我們的需求時颈渊,我們可以自定義自己的 NSOperation 子類,添加我們想要的功能终佛。
可以通過重寫 main 或者 start 方法 來定義自己的 NSOperation 對象。重寫main方法比較簡單雾家,我們不需要管理操作的狀態(tài)屬性 isExecuting 和 isFinished铃彰。當 main 執(zhí)行完返回的時候,操作就結束(isFinished為YES)芯咧。
簡單示例:
//NonConcurrentOperation.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NonConcurrentOperation : NSOperation
@end
NS_ASSUME_NONNULL_END
//NonConcurrentOperation.m
#import "NonConcurrentOperation.h"
@implementation NonConcurrentOperation
- (void)main
{
for (NSInteger i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"任務牙捉,---%@", [NSThread currentThread]);
}
}
@end
使用NonConcurrentOperation類
- (void)nonConcurrentOperationAction
{
//不加入隊列
NonConcurrentOperation *operation = [[NonConcurrentOperation alloc] init];
[operation start];
NSLog(@"---------");
//加入隊列
NonConcurrentOperation *operation1 = [[NonConcurrentOperation alloc] init];
NSOperationQueue *queue = [NSOperationQueue new];
[queue addOperation:operation1];
}
打印結果:
2019-07-22 15:02:22.924678+0800 NSOperationDemo[4590:129265] 任務,---<NSThread: 0x600000eda900>{number = 1, name = main}
2019-07-22 15:02:23.926285+0800 NSOperationDemo[4590:129265] 任務敬飒,---<NSThread: 0x600000eda900>{number = 1, name = main}
2019-07-22 15:02:23.926549+0800 NSOperationDemo[4590:129265] ---------
2019-07-22 15:02:24.932274+0800 NSOperationDemo[4590:129326] 任務邪铲,---<NSThread: 0x600000eb6380>{number = 3, name = (null)}
2019-07-22 15:02:25.934638+0800 NSOperationDemo[4590:129326] 任務,---<NSThread: 0x600000eb6380>{number = 3, name = (null)}
在默認情況下无拗,operation 是同步執(zhí)行的带到,也就是說在調用它的 start 方法的線程中執(zhí)行它們的任務。而在 operation 和 operation queue 結合使用時英染,operation queue 可以為非并發(fā)的 operation 提供線程揽惹,因此,大部分的 operation 仍然可以異步執(zhí)行四康。
如果你想要手動地執(zhí)行一個 operation 搪搏,又想這個 operation 能夠異步執(zhí)行的話,你需要做一些額外的配置來讓你的 operation 支持并發(fā)執(zhí)行闪金。
下面列舉了一些你可能需要重寫的方法:
start :必須的疯溺,所有并發(fā)執(zhí)行的 operation 都必須要重寫這個方法,替換掉 NSOperation 類中的默認實現(xiàn)。start 方法是一個 operation 的起點囱嫩,我們可以在這里配置任務執(zhí)行的線程或者一些其它的執(zhí)行環(huán)境恃疯。另外,需要特別注意的是挠说,在我們重寫的 start 方法中一定不要調用父類的實現(xiàn)澡谭;
main :可選的,通常這個方法就是專門用來實現(xiàn)與該 operation 相關聯(lián)的任務的损俭。盡管我們可以直接在 start 方法中執(zhí)行我們的任務蛙奖,但是用 main 方法來實現(xiàn)我們的任務可以使設置代碼和任務代碼得到分離,從而使 operation 的結構更清晰杆兵;
isExecuting 和 isFinished :必須的雁仲,并發(fā)執(zhí)行的 operation 需要負責配置它們的執(zhí)行環(huán)境,并且向外界客戶報告執(zhí)行環(huán)境的狀態(tài)琐脏。因此攒砖,一個并發(fā)執(zhí)行的 operation 必須要維護一些狀態(tài)信息,用來記錄它的任務是否正在執(zhí)行日裙,是否已經完成執(zhí)行等吹艇。此外,當這兩個方法所代表的值發(fā)生變化時昂拂,我們需要生成相應的 KVO 通知受神,以便外界能夠觀察到這些狀態(tài)的變化;
isConcurrent :必須的格侯,這個方法的返回值用來標識一個 operation 是否是并發(fā)的 operation 鼻听,我們需要重寫這個方法并返回 YES 。
注意:當一個 operation 開始執(zhí)行后联四,它會一直執(zhí)行它的任務直到完成或被取消為止撑碴。我們可以在任意時間點取消一個 operation ,甚至是在它還未開始執(zhí)行之前朝墩。為了讓我們自定義的 operation 能夠支持取消事件醉拓,我們需要在代碼中定期地檢查 isCancelled 方法的返回值,一旦檢查到這個方法返回 YES 鱼辙,我們就需要立即停止執(zhí)行接下來的任務廉嚼。根據蘋果官方的說法,isCancelled 方法本身是足夠輕量的倒戏,所以就算是頻繁地調用它也不會給系統(tǒng)帶來太大的負擔怠噪。
簡單示例:
//MyOperation.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface MyOperation : NSOperation
@end
NS_ASSUME_NONNULL_END
//MyOperation.m
#import "MyOperation.h"
@interface MyOperation()
@property (nonatomic, assign) BOOL executing;
@property (nonatomic, assign) BOOL finished;
@end
@implementation MyOperation
@synthesize executing = _executing;
@synthesize finished = _finished;
#pragma mark - start
- (void)start
{
NSLog(@"任務開始");
//開始執(zhí)行
self.executing = YES;
//執(zhí)行循環(huán)操作
for (NSInteger i = 0; i < 10; ++i) {
if ( self.cancelled ) {
//如果任務已經被取消了,更改狀態(tài)后杜跷,返回
self.executing = NO;
self.finished = YES;
NSLog(@"任務取消傍念,退出");
return;
}
NSLog(@"Task %ld, thread:%@,executing=%d,cancelled=%d,finished=%d,operationCount=%lu", (long)i, [NSThread currentThread], self.executing, self.cancelled, self.finished, (unsigned long)[[NSOperationQueue currentQueue] operationCount]);
[NSThread sleepForTimeInterval:0.2];
}
NSLog(@"任務結束");
self.executing = NO;
self.finished = YES;
NSLog(@"end...thread:%@,executing=%d,cancelled=%d,finished=%d,operationCount=%lu", [NSThread currentThread], self.executing, self.cancelled, self.finished, (unsigned long)[[NSOperationQueue currentQueue] operationCount]);
}
//任務是否是并發(fā)
- (BOOL)isAsynchronous
{
return YES;
}
#pragma mark - getter & setter
- (void)setExecuting:(BOOL)executing
{
[self willChangeValueForKey:@"executing"];
_executing = executing;
[self didChangeValueForKey:@"executing"];
}
- (BOOL)executing
{
return _executing;
}
- (void)setFinished:(BOOL)finished
{
[self willChangeValueForKey:@"finished"];
_finished = finished;
[self didChangeValueForKey:@"finished"];
}
@end
使用MyOperation類:
- (void)customOperationAction
{
MyOperation *operation = [[MyOperation alloc] init];
NSOperationQueue *queue = [NSOperationQueue new];
[queue addOperation:operation];
}
打印結果:
2019-07-22 15:28:09.950993+0800 NSOperationDemo[5038:140737] 任務開始
2019-07-22 15:28:09.951745+0800 NSOperationDemo[5038:140737] Task 0, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:10.153603+0800 NSOperationDemo[5038:140737] Task 1, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:10.354902+0800 NSOperationDemo[5038:140737] Task 2, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:10.557117+0800 NSOperationDemo[5038:140737] Task 3, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:10.758551+0800 NSOperationDemo[5038:140737] Task 4, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:10.960494+0800 NSOperationDemo[5038:140737] Task 5, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:11.163521+0800 NSOperationDemo[5038:140737] Task 6, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:11.363928+0800 NSOperationDemo[5038:140737] Task 7, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:11.566943+0800 NSOperationDemo[5038:140737] Task 8, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:11.767499+0800 NSOperationDemo[5038:140737] Task 9, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:11.969353+0800 NSOperationDemo[5038:140737] 任務結束
2019-07-22 15:28:11.969781+0800 NSOperationDemo[5038:140737] end...thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=0,cancelled=0,finished=1,operationCount=1
即使一個 operation 是被 cancel 掉了矫夷,我們仍然需要手動觸發(fā) isFinished 的 KVO 通知。因為當一個 operation 依賴其他 operation 時憋槐,它會觀察所有其他 operation 的 isFinished 的值的變化双藕,只有當它依賴的所有 operation 的 isFinished 的值為 YES 時,這個 operation 才能夠開始執(zhí)行阳仔。因此忧陪,如果一個我們自定義的 operation 被取消了但卻沒有手動觸發(fā) isFinished 的 KVO 通知的話,那么所有依賴它的 operation 都不會執(zhí)行近范。
4.4 NSOperationQueue 控制串行執(zhí)行嘶摊、并發(fā)執(zhí)行
操作隊列中有個關鍵屬性 maxConcurrentOperationCount,叫做最大并發(fā)操作數评矩。用來控制一個特定隊列中可以有多少個操作同時參與并發(fā)執(zhí)行叶堆。
maxConcurrentOperationCount的值為:
- -1,默認情況就是-1斥杜,表示不進行限制虱颗,可進行并發(fā)執(zhí)行;
- 0蔗喂,不會有任何操作執(zhí)行忘渔;
- 1,隊列為串行隊列缰儿,只能串行執(zhí)行辨萍;
- 大于1,隊列為并發(fā)隊列返弹,這個值也不是無限大,系統(tǒng)也會自動調整為 min{自己設定的值爪飘,系統(tǒng)設定的默認最大值}义起;
可以通過
@property (class, readonly, strong) NSOperationQueue *mainQueue API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
獲取到主隊列。
簡單示例:
- (void)testMacConcurrentOperationCount
{
//創(chuàng)建 operation1
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
for (NSInteger i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"任務1:---%@", [NSThread currentThread]);
}
}];
//創(chuàng)建 operation2
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
for (NSInteger i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"任務2:---%@", [NSThread currentThread]);
}
}];
//創(chuàng)建 operation3
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
for (NSInteger i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"任務3:---%@", [NSThread currentThread]);
}
}];
//創(chuàng)建 operation4
NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{
for (NSInteger i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"任務4:---%@", [NSThread currentThread]);
}
}];
NSOperationQueue *queue = [NSOperationQueue new];
// queue.maxConcurrentOperationCount = 0;
// queue.maxConcurrentOperationCount = 1;
// queue.maxConcurrentOperationCount = 2;
[queue addOperation:operation1];
[queue addOperation:operation2];
[queue addOperation:operation3];
[queue addOperation:operation4];
}
最大并發(fā)操作數為0 打印結果:
沒有任何輸出师崎。
最大并發(fā)操作數為1 打印結果:
2019-07-22 15:44:09.151837+0800 NSOperationDemo[5356:150014] 任務1:---<NSThread: 0x600002d5a340>{number = 3, name = (null)}
2019-07-22 15:44:10.155735+0800 NSOperationDemo[5356:150014] 任務1:---<NSThread: 0x600002d5a340>{number = 3, name = (null)}
2019-07-22 15:44:11.161370+0800 NSOperationDemo[5356:150012] 任務2:---<NSThread: 0x600002d596c0>{number = 4, name = (null)}
2019-07-22 15:44:12.167015+0800 NSOperationDemo[5356:150012] 任務2:---<NSThread: 0x600002d596c0>{number = 4, name = (null)}
2019-07-22 15:44:13.172285+0800 NSOperationDemo[5356:150014] 任務3:---<NSThread: 0x600002d5a340>{number = 3, name = (null)}
2019-07-22 15:44:14.172991+0800 NSOperationDemo[5356:150014] 任務3:---<NSThread: 0x600002d5a340>{number = 3, name = (null)}
2019-07-22 15:44:15.175897+0800 NSOperationDemo[5356:150012] 任務4:---<NSThread: 0x600002d596c0>{number = 4, name = (null)}
2019-07-22 15:44:16.181339+0800 NSOperationDemo[5356:150012] 任務4:---<NSThread: 0x600002d596c0>{number = 4, name = (null)}
最大并發(fā)操作數為2 打印結果:
2019-07-22 15:45:02.487935+0800 NSOperationDemo[5377:150560] 任務1:---<NSThread: 0x60000040f0c0>{number = 4, name = (null)}
2019-07-22 15:45:02.487965+0800 NSOperationDemo[5377:150558] 任務2:---<NSThread: 0x600000428740>{number = 3, name = (null)}
2019-07-22 15:45:03.488880+0800 NSOperationDemo[5377:150558] 任務2:---<NSThread: 0x600000428740>{number = 3, name = (null)}
2019-07-22 15:45:03.488888+0800 NSOperationDemo[5377:150560] 任務1:---<NSThread: 0x60000040f0c0>{number = 4, name = (null)}
2019-07-22 15:45:04.494115+0800 NSOperationDemo[5377:150561] 任務4:---<NSThread: 0x60000040e940>{number = 5, name = (null)}
2019-07-22 15:45:04.494168+0800 NSOperationDemo[5377:150559] 任務3:---<NSThread: 0x60000040eb80>{number = 6, name = (null)}
2019-07-22 15:45:05.498621+0800 NSOperationDemo[5377:150561] 任務4:---<NSThread: 0x60000040e940>{number = 5, name = (null)}
2019-07-22 15:45:05.498621+0800 NSOperationDemo[5377:150559] 任務3:---<NSThread: 0x60000040eb80>{number = 6, name = (null)}
當最大并發(fā)操作數為0時默终,沒有任何操作執(zhí)行;
當最大并發(fā)操作數為1時犁罩,操作是按順序串行執(zhí)行的齐蔽;
當最大操作并發(fā)數為2時,操作是并發(fā)執(zhí)行的床估,可以同時執(zhí)行兩個操作含滴;
5. 操作之間的依賴
通過操作依賴,我們可以很方便的控制操作之間的執(zhí)行先后順序丐巫。
配置 operation 的依賴關系主要涉及到 NSOperation 類中的以下兩個方法:
//添加依賴谈况,使當前操作依賴于操作 op 的完成
- (void)addDependency:(NSOperation *)op;
//移除依賴勺美,取消當前操作對操作 op 的依賴
- (void)removeDependency:(NSOperation *)op;
簡單示例:
- (void)testDependency
{
//創(chuàng)建 operation1
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
for (NSInteger i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"任務1:---%@", [NSThread currentThread]);
}
}];
//創(chuàng)建 operation2
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
for (NSInteger i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"任務2:---%@", [NSThread currentThread]);
}
}];
[operation1 addDependency:operation2];
NSOperationQueue *queue = [NSOperationQueue new];
[queue addOperation:operation1];
[queue addOperation:operation2];
}
打印結果:
2019-07-22 16:29:06.178541+0800 NSOperationDemo[5934:164383] 任務2:---<NSThread: 0x600000d8dbc0>{number = 3, name = (null)}
2019-07-22 16:29:07.184051+0800 NSOperationDemo[5934:164383] 任務2:---<NSThread: 0x600000d8dbc0>{number = 3, name = (null)}
2019-07-22 16:29:08.189099+0800 NSOperationDemo[5934:164386] 任務1:---<NSThread: 0x600000de8380>{number = 4, name = (null)}
2019-07-22 16:29:09.194669+0800 NSOperationDemo[5934:164386] 任務1:---<NSThread: 0x600000de8380>{number = 4, name = (null)}
注意:
- 不要造成循環(huán)依賴,不然會出現(xiàn)等待的情況碑韵,導致操作都不發(fā)執(zhí)行赡茸;
- 另外添加依賴要在隊列添加操作之前,因為隊列添加操作后祝闻,什么時候執(zhí)行是系統(tǒng)決定的占卧,有可能執(zhí)行會比添加依賴還更快;
6. NSOperation 隊列中的優(yōu)先級
對于被添加到 操作隊列 中的 操作 來說联喘,決定它們執(zhí)行順序的第一要素是它們的 isReady 狀態(tài)华蜒,其次是它們在隊列中的優(yōu)先級。操作 的 isReady 狀態(tài)取決于它的依賴關系耸袜,而在隊列中的優(yōu)先級則是 操作本身的屬性友多。默認情況下,所有新創(chuàng)建的操作在隊列中的優(yōu)先級都是 NSOperationQueuePriorityNormal 的堤框,但是我們可以根據需要通過改變queuePriority屬性值
@property NSOperationQueuePriority queuePriority;
來提高或降低 operation 的隊列優(yōu)先級域滥。
優(yōu)先級的取值有:
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
需要注意的是,隊列優(yōu)先級只應用于相同操作隊列中的操作之間蜈抓,不同操作隊列中的操作不受此影響启绰。
另外,隊列優(yōu)先級和依賴關系之間的關系:
- 操作的隊列優(yōu)先級只決定當前所有 isReady 狀態(tài)為 YES 的操作的執(zhí)行順序沟使。比如委可,在一個操作隊列中,有一個高優(yōu)先級和一個低優(yōu)先級的操作 腊嗡,并且它們的 isReady 狀態(tài)都為 YES 着倾,那么高優(yōu)先級的 operation 將會優(yōu)先執(zhí)行。而如果這個高優(yōu)先級的操作的 isReady 狀態(tài)為 NO 燕少,而低優(yōu)先級的操作的 isReady 狀態(tài)為 YES 的話卡者,那么這個低優(yōu)先級的操作反而會優(yōu)先執(zhí)行妥曲;
- 操作的隊列中有一個準備就緒狀態(tài)的操作和一個未準備就緒的操作脐湾,未準備就緒的操作優(yōu)先級比準備就緒的操作優(yōu)先級高。但是準備就緒的操作依賴于未準備就緒操作的完成颤枪。雖然準備就緒的操作優(yōu)先級低底挫,也會優(yōu)先執(zhí)行恒傻。優(yōu)先級不能取代依賴關系。如果要控制操作間的執(zhí)行順序建邓,則必須使用依賴關系盈厘;
7. 線程之間的通信
場景:耗時操作,比如下載操作會放在子線程中處理涝缝,當子線程結束后扑庞,需要回到主線程中設置刷新UI譬重。
簡單示例:
- (void)threadCommunication
{
//1 創(chuàng)建隊列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//2 創(chuàng)建操作
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
//2.1 執(zhí)行耗時操作
NSURL *url = [NSURL URLWithString:@"https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1902385933,516700697&fm=26&gp=0.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
NSLog(@"圖片下載完畢-----%@",[NSThread currentThread]);
//2.2 獲取主隊列,回到主線程執(zhí)行block里面的任務
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
//在主線程中罐氨,刷新UI
self.imageView.image = image;
}];
}];
//3 加入隊列
[queue addOperation:operation];
}
8. 暫停和恢復操作隊列
如果我們想要暫停和恢復執(zhí)行操作隊列中的操作 臀规,可以通過調用 操作隊列的 setSuspended: 方法來實現(xiàn)這個目的。不過需要注意的是栅隐,暫停執(zhí)行操作隊列并不能使正在執(zhí)行的操作暫停執(zhí)行塔嬉,而只是簡單地暫停調度新的操作。另外租悄,我們并不能單獨地暫停執(zhí)行一個操作谨究,除非直接 cancel 掉。
暫停和取消的區(qū)別就在于:暫停操作之后還可以恢復操作泣棋,繼續(xù)向下執(zhí)行胶哲;而取消操作之后,所有的操作就清空了潭辈,無法再接著執(zhí)行剩下的操作鸯屿。
參考資料:
iOS 并發(fā)編程之 Operation Queues
Demo傳送門
over!