NSOperation
看一下官方的介紹:
The NSOperation class itself is an abstract base class that must be subclassed in order to do any useful work.
NSOperation
是一個抽象基類,我們需要使用它的子類來完成工作。從使用上來說NSOperation
就是對GCD
中block
的封裝,它表示一個要被執(zhí)行的任務(wù)。
與GCD
相比,NSOperation
可以更好的控制任務(wù),包括暫停,繼續(xù),取消這類操作并且提供了當(dāng)前任務(wù)的狀態(tài)供開發(fā)者監(jiān)聽。而在GCD
中要實現(xiàn)這些需求可能會需要其他很多復(fù)雜的代碼。
NSOperation基本功能
1.基本的操作和狀態(tài)
先看一下NSOperation.h中的NSOperation類。
- (void)start;
- (void)main;
@property (readonly, getter=isCancelled) BOOL cancelled;
- (void)cancel;
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isConcurrent) BOOL concurrent; // To be deprecated; use and override 'asynchronous' below
@property (readonly, getter=isAsynchronous) BOOL asynchronous NS_AVAILABLE(10_8, 7_0);
@property (readonly, getter=isReady) BOOL ready;
NSOperation提供了任務(wù)的狀態(tài),執(zhí)行,完畢,取消等。并且提供了一個start方法,這表明NSOperation是可以直接執(zhí)行的。這里要注意NSOperation執(zhí)行是一個同步操作,任務(wù)會執(zhí)行在當(dāng)前運行的線程上。
2.依賴關(guān)系
- (void)addDependency:(NSOperation *)op;
- (void)removeDependency:(NSOperation *)op;
@property (readonly, copy) NSArray<NSOperation *> *dependencies;
NSOperation還提供了一個依賴功能,通過依賴可以控制任務(wù)執(zhí)行的順序。
3.任務(wù)優(yōu)先級
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
@property NSOperationQueuePriority queuePriority;
與GCD
不同,GCD
只能設(shè)置隊列的優(yōu)先級,而NSOperation
及其子類可以設(shè)置任務(wù)的優(yōu)先級。需要注意的是,NSOperationQueue
也不能完全保證優(yōu)先級高的任務(wù)一定先執(zhí)行。
系統(tǒng)提供的子類
NSInvocationOperation
這個類的使用類似于給UIbutton添加一個方法。需要一個對象和一個Selector。
NSInvocationOperation *inOp = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testInOp) object:nil];
NSLog(@"begin");
[inOp start];
NSLog(@"end");
- (void)testInOp {
NSLog(@"%@",[NSThread currentThread]);
}
打印結(jié)果:
2017-09-19 16:12:55.938 learn_09_NSOperation[3507:146749] begin
2017-09-19 16:12:55.938 learn_09_NSOperation[3507:146749] <NSThread: 0x60000006e700>{number = 1, name = main}
2017-09-19 16:12:55.939 learn_09_NSOperation[3507:146749] end
可以看出,這個任務(wù)相是同步執(zhí)行的。再修改一下例子:
NSInvocationOperation *inOp = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testInOp) object:nil];
NSLog(@"begin");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[inOp start];
});
NSLog(@"end");
打印結(jié)果
2017-09-19 16:14:44.266 learn_09_NSOperation[3536:147731] begin
2017-09-19 16:14:44.266 learn_09_NSOperation[3536:147731] end
2017-09-19 16:14:44.267 learn_09_NSOperation[3536:147761] <NSThread: 0x60000007c240>{number = 3, name = (null)}
可以看出,任務(wù)可以直接在子線程啟動,就變成一個異步執(zhí)行的任務(wù)。其實這里還是在其他線程中同步執(zhí)行了該任務(wù)
NSBlockOperation
與之前的NSInvocationOperation比較,NSBlockOperation在使用上更加的方便,它提供一個block代碼塊來執(zhí)行任務(wù)。
NSBlockOperation *bOp = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
[bOp start];
執(zhí)行結(jié)果:<NSThread: 0x60800006f280>{number = 1, name = main}
我們可以看出基本使用上與NSInvocationOperation結(jié)果一致。但不同的是NSBlockOperation支持了并發(fā)執(zhí)行一個或多個block。
NSBlockOperation *bOp = [[NSBlockOperation alloc] init];
[bOp addExecutionBlock:^{
NSLog(@"1--%@",[NSThread currentThread]);
NSLog(@"2--%@",[NSThread currentThread]);
NSLog(@"3--%@",[NSThread currentThread]);
}];
[bOp addExecutionBlock:^{
NSLog(@"4--%@",[NSThread currentThread]);
NSLog(@"5--%@",[NSThread currentThread]);
NSLog(@"6--%@",[NSThread currentThread]);
}];
[bOp addExecutionBlock:^{
NSLog(@"7--%@",[NSThread currentThread]);
NSLog(@"8--%@",[NSThread currentThread]);
NSLog(@"9--%@",[NSThread currentThread]);
}];
[bOp start];
執(zhí)行結(jié)果:
2017-09-19 16:22:53.392 learn_09_NSOperation[3631:152054] 1--<NSThread: 0x60800007ad80>{number = 1, name = main}
2017-09-19 16:22:53.392 learn_09_NSOperation[3631:152054] 2--<NSThread: 0x60800007ad80>{number = 1, name = main}
2017-09-19 16:22:53.392 learn_09_NSOperation[3631:152054] 3--<NSThread: 0x60800007ad80>{number = 1, name = main}
2017-09-19 16:22:53.392 learn_09_NSOperation[3631:152091] 4--<NSThread: 0x60800007c440>{number = 3, name = (null)}
2017-09-19 16:22:53.392 learn_09_NSOperation[3631:152089] 7--<NSThread: 0x608000266300>{number = 4, name = (null)}
2017-09-19 16:22:53.393 learn_09_NSOperation[3631:152091] 5--<NSThread: 0x60800007c440>{number = 3, name = (null)}
2017-09-19 16:22:53.393 learn_09_NSOperation[3631:152089] 8--<NSThread: 0x608000266300>{number = 4, name = (null)}
2017-09-19 16:22:53.394 learn_09_NSOperation[3631:152091] 6--<NSThread: 0x60800007c440>{number = 3, name = (null)}
2017-09-19 16:22:53.394 learn_09_NSOperation[3631:152089] 9--<NSThread: 0x608000266300>{number = 4, name = (null)}
從中我們可以看出每一個代碼塊執(zhí)行在一條線程上,每條線程之間彼此無關(guān),任務(wù)執(zhí)行順序是并發(fā)執(zhí)行的。
為了更好的操作這些任務(wù),我們需要結(jié)合NSOperationQueue
來管理這些任務(wù)。這樣,我們任務(wù)
與隊列
的概念就結(jié)合起來了。
自定義NSOperation的子類
自定義串行NSOperation,只需要在子類中重寫main
函數(shù)
自定義并行NSOperation,需要子類實現(xiàn)start
,main
,isExecuting
,isFinished
,isConcurrent
,isAsynchronous
方法
一般來說我們使用系統(tǒng)提供的子類就可以完成絕大部分需求了。關(guān)于自定義子類可參見這篇文章NSOperation 自定義子類實現(xiàn)非并發(fā)和并發(fā)操作
NSOperationQueue
1.隊列的基本使用
NSOperationQueue
類似于GCD
中的隊列。我們知道GCD
中的隊列有三種:主隊列
、串行隊
列和并發(fā)隊列
。NSOperationQueue
更簡單,只有兩種:主隊列
和非主隊列
。
默認(rèn)情況下,我們創(chuàng)建的NSOperationQueue隊列都是非主隊列。
我們可以通過NSOperationQueue.mainQueue
來獲取主隊列。
NSOperationQueue
的主隊列是串行隊列,而且其中所有NSOperation
都會在主線程中執(zhí)行。
對于非主隊列來說,一旦一個NSOperation
被放入其中,那這個NSOperation
一定是并發(fā)執(zhí)行的。因為NSOperationQueue
會為每一個NSOperation
創(chuàng)建線程并調(diào)用它的start
方法。
但這并不意味著NSOperationQueue
不支持串行執(zhí)行。NSOperationQueue
提供了maxConcurrentOperationCount
屬性,讓我們設(shè)置隊列中的最大并發(fā)數(shù)。當(dāng)設(shè)置為1時,隊列會按串行方式執(zhí)行里面的任務(wù)。
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;
for (int i=0; i < 10; ++i) {
NSBlockOperation *bop = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%d----%@",i,[NSThread currentThread]);
}];
[queue addOperation:bop];
}
打印結(jié)果:
2017-09-19 16:38:09.401 learn_09_NSOperation[3763:158562] 0----<NSThread: 0x608000079500>{number = 3, name = (null)}
2017-09-19 16:38:09.402 learn_09_NSOperation[3763:158562] 1----<NSThread: 0x608000079500>{number = 3, name = (null)}
2017-09-19 16:38:09.402 learn_09_NSOperation[3763:158562] 2----<NSThread: 0x608000079500>{number = 3, name = (null)}
2017-09-19 16:38:09.403 learn_09_NSOperation[3763:158562] 3----<NSThread: 0x608000079500>{number = 3, name = (null)}
2017-09-19 16:38:09.403 learn_09_NSOperation[3763:158562] 4----<NSThread: 0x608000079500>{number = 3, name = (null)}
2017-09-19 16:38:09.405 learn_09_NSOperation[3763:158562] 5----<NSThread: 0x608000079500>{number = 3, name = (null)}
2017-09-19 16:38:09.405 learn_09_NSOperation[3763:158562] 6----<NSThread: 0x608000079500>{number = 3, name = (null)}
2017-09-19 16:38:09.406 learn_09_NSOperation[3763:158562] 7----<NSThread: 0x608000079500>{number = 3, name = (null)}
2017-09-19 16:38:09.406 learn_09_NSOperation[3763:158562] 8----<NSThread: 0x608000079500>{number = 3, name = (null)}
2017-09-19 16:38:09.407 learn_09_NSOperation[3763:158562] 9----<NSThread: 0x608000079500>{number = 3, name = (null)}
從結(jié)果可以看出,在最大并發(fā)為1的情況下,隊列是異步的串行執(zhí)行的。當(dāng)然如果我們不設(shè)置最大并發(fā)數(shù)或者>1的情況下,系統(tǒng)會自動為每個任務(wù)分配線程,所有的線程調(diào)度都交給系統(tǒng)控制。
2.任務(wù)管理
由于我們的NSOperation對象擁有自己的任務(wù)狀態(tài),為了管理隊列中的NSOperation任務(wù),系統(tǒng)為我們提供了以下方法來獲取隊列中的任務(wù)。
@property (readonly, copy) NSArray<__kindof NSOperation *> *operations;
@property (readonly) NSUInteger operationCount NS_AVAILABLE(10_6, 4_0);
我們可以獲取到隊列中的每一個任務(wù)并操作它。
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
for (int i=0; i < 10; ++i) {
NSBlockOperation *bop = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%d----%@",i,[NSThread currentThread]);
}];
[queue addOperation:bop];
}
NSBlockOperation *op = queue.operations.lastObject;
NSLog(@"%d",op.isCancelled);
[op cancel];
NSLog(@"%d",op.isCancelled);
在執(zhí)行前將任務(wù)取消掉,則該任務(wù)將不會被執(zhí)行。
當(dāng)然,在任務(wù)較多的情況下,這樣取消任務(wù)實在是太可愛了。NSOperationQueue 提供了cancelAllOperations
方法,幫我們直接取消掉所有的任務(wù)。
3.其他操作
隊列優(yōu)先級
像CGD一樣,NSOperationQueue同樣提供了優(yōu)先級設(shè)置,我們可以根據(jù)不同場景選擇對應(yīng)的優(yōu)先級。
typedef NS_ENUM(NSInteger, NSQualityOfService) {
/* UserInteractive QoS is used for work directly involved in providing an interactive UI such as processing events or drawing to the screen. */
NSQualityOfServiceUserInteractive = 0x21,
/* UserInitiated QoS is used for performing work that has been explicitly requested by the user and for which results must be immediately presented in order to allow for further user interaction. For example, loading an email after a user has selected it in a message list. */
NSQualityOfServiceUserInitiated = 0x19,
/* Utility QoS is used for performing work which the user is unlikely to be immediately waiting for the results. This work may have been requested by the user or initiated automatically, does not prevent the user from further interaction, often operates at user-visible timescales and may have its progress indicated to the user by a non-modal progress indicator. This work will run in an energy-efficient manner, in deference to higher QoS work when resources are constrained. For example, periodic content updates or bulk file operations such as media import. */
NSQualityOfServiceUtility = 0x11,
/* Background QoS is used for work that is not user initiated or visible. In general, a user is unaware that this work is even happening and it will run in the most efficient manner while giving the most deference to higher QoS work. For example, pre-fetching content, search indexing, backups, and syncing of data with external systems. */
NSQualityOfServiceBackground = 0x09,
/* Default QoS indicates the absence of QoS information. Whenever possible QoS information will be inferred from other sources. If such inference is not possible, a QoS between UserInitiated and Utility will be used. */
NSQualityOfServiceDefault = -1
} NS_ENUM_AVAILABLE(10_10, 8_0);
@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);
等待執(zhí)行
- (void)waitUntilAllOperationsAreFinished
NSOperationQueue提供了這樣一個函數(shù),讓調(diào)用該函數(shù)前的所有任務(wù)執(zhí)行完畢以后才會執(zhí)行后續(xù)的操作
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
for (int i=0; i < 3; ++i) {
NSBlockOperation *bop = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%d----%@",i,[NSThread currentThread]);
}];
[queue addOperation:bop];
}
NSLog(@"wait");
[queue waitUntilAllOperationsAreFinished];
NSLog(@"go on");
打印結(jié)果:
2017-09-19 16:58:22.864 learn_09_NSOperation[3908:166871] wait
2017-09-19 16:58:22.865 learn_09_NSOperation[3908:166906] 1----<NSThread: 0x6000002620c0>{number = 3, name = (null)}
2017-09-19 16:58:22.865 learn_09_NSOperation[3908:166907] 2----<NSThread: 0x608000078940>{number = 5, name = (null)}
2017-09-19 16:58:22.865 learn_09_NSOperation[3908:166909] 0----<NSThread: 0x600000262600>{number = 4, name = (null)}
2017-09-19 16:58:22.865 learn_09_NSOperation[3908:166871] go on
當(dāng)任務(wù)執(zhí)行完畢后,才會繼續(xù)執(zhí)行waitUntilAllOperationsAreFinished
下面的代碼,可以看出waitUntilAllOperationsAreFinished
會阻塞當(dāng)前的線程來等待隊列中任務(wù)執(zhí)行。
暫停隊列
NSOperationQueue 提供了suspended
的屬性,顧名思義這個屬性用來控制隊列是否暫停。但它的暫停方法并不是那么直接。官方文檔上是這么說的:
如果這個值設(shè)置為 NO,那說明這個隊列已經(jīng)準(zhǔn)備好了可以執(zhí)行了。如果這個值設(shè)置為 YES,那么已經(jīng)添加到隊列中的操作還是可以執(zhí)行了,而后面繼續(xù)添加進(jìn)隊列中的操作才處于暫停狀態(tài),直到你再次將這個值設(shè)置為 NO 時,后面加入的操作才會繼續(xù)執(zhí)行。這個屬性的默認(rèn)值是 NO。
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
for (int i=0; i < 4; ++i) {
NSBlockOperation *bop = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%d",i);
}];
if(i == 2) {
[queue setSuspended:YES];
}
[queue addOperation:bop];
}
//模擬耗時操作后開啟隊列
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"re start");
[queue setSuspended:NO];
});
打印結(jié)果:
2017-09-19 17:10:23.796 learn_09_NSOperation[3994:171960] 0
2017-09-19 17:10:23.796 learn_09_NSOperation[3994:171959] 1
2017-09-19 17:10:25.796 learn_09_NSOperation[3994:171922] re start
2017-09-19 17:10:25.796 learn_09_NSOperation[3994:171959] 2
2017-09-19 17:10:25.796 learn_09_NSOperation[3994:171960] 3
一個例子:利用任務(wù)依賴實現(xiàn)一個規(guī)定順序的任務(wù)隊列
假設(shè)我們10個任務(wù),我們需要異步執(zhí)行,先順序執(zhí)行后5條任務(wù)再順序執(zhí)行前5條任務(wù)。通過NSOperation的依賴功能與NSOperationQueue結(jié)合來滿足這個需求。(代碼寫得不好,只是為了表現(xiàn)NSOperationQueue的使用靈活與簡便)
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSMutableArray *ops = [NSMutableArray array];
for (int i = 0; i < 10; ++i) {
NSBlockOperation *bop = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%d---%@",i,[NSThread currentThread]);
}];
[ops addObject:bop];
}
//
[ops enumerateObjectsUsingBlock:^(NSBlockOperation *obj, NSUInteger idx, BOOL * _Nonnull stop) {
if(idx > 0 && idx < 5){
NSBlockOperation *old = ops[idx - 1];
[obj addDependency:old];
}
else if (idx > 5) {
NSBlockOperation *old = ops[idx - 1];
[obj addDependency:old];
}
if(idx == ops.count - 1) {
NSBlockOperation *fst = ops.firstObject;
[fst addDependency:obj];
}
}];
[queue addOperations:ops waitUntilFinished:NO];
打印結(jié)果:
2017-09-19 17:16:33.256 learn_09_NSOperation[4037:174132] 5---<NSThread: 0x608000267c80>{number = 3, name = (null)}
2017-09-19 17:16:33.258 learn_09_NSOperation[4037:174132] 6---<NSThread: 0x608000267c80>{number = 3, name = (null)}
2017-09-19 17:16:33.260 learn_09_NSOperation[4037:174132] 7---<NSThread: 0x608000267c80>{number = 3, name = (null)}
2017-09-19 17:16:33.261 learn_09_NSOperation[4037:174134] 8---<NSThread: 0x608000262d80>{number = 4, name = (null)}
2017-09-19 17:16:33.262 learn_09_NSOperation[4037:174134] 9---<NSThread: 0x608000262d80>{number = 4, name = (null)}
2017-09-19 17:16:33.265 learn_09_NSOperation[4037:174134] 0---<NSThread: 0x608000262d80>{number = 4, name = (null)}
2017-09-19 17:16:33.283 learn_09_NSOperation[4037:174134] 1---<NSThread: 0x608000262d80>{number = 4, name = (null)}
2017-09-19 17:16:33.292 learn_09_NSOperation[4037:174134] 2---<NSThread: 0x608000262d80>{number = 4, name = (null)}
2017-09-19 17:16:33.298 learn_09_NSOperation[4037:174132] 3---<NSThread: 0x608000267c80>{number = 3, name = (null)}
2017-09-19 17:16:33.299 learn_09_NSOperation[4037:174134] 4---<NSThread: 0x608000262d80>{number = 4, name = (null)}
可以看出先依次執(zhí)行了5-9,再依次執(zhí)行了0-4。并且在設(shè)置了依賴關(guān)系后,隊列的線程調(diào)度也會自動變化。保證了程序性能。
NSOperation與GCD比較
NSOperation是CGD的封裝,相比于GCD而言,NSOperation可以更加方便的控制任務(wù),設(shè)置依賴,執(zhí)行,暫停,取消,控制優(yōu)先級等。NSOperation是對線程的高度抽象,在項目中使用它,會使項目的程序結(jié)構(gòu)更好,子類化NSOperation的設(shè)計思路,是具有面向?qū)ο蟮膬?yōu)點(復(fù)用、封裝),使得實現(xiàn)是多線程支持,而接口簡單。
GCD以block為單位,代碼簡潔。同時GCD中的隊列、組、信號量、source、barriers都是組成并行編程的基本原語。對于一次性的計算,或是僅僅為了加快現(xiàn)有方法的運行速度,選擇輕量化的GCD就更加方便。
小結(jié)
通過NSOperation與NSOperationQueue的結(jié)合使用,我們可以實現(xiàn)與GCD相同的多線程并發(fā)操作,并且能夠更方便的對異步任務(wù)進(jìn)行管理控制。
相關(guān)文章
還在用GCD?來看看 NSOperation 吧
NSOperation v.s GCD
iOS多線程編程總結(jié)
iOS多線程(三):NSOperationQueue 的使用