概述
iOS并發(fā)編程中,把每個(gè)并發(fā)任務(wù)定義為一個(gè)Operation。NSOperation是一個(gè)抽象類,無(wú)法直接使用,它只定義了Operation的一些基本方法。我們需要?jiǎng)?chuàng)建一個(gè)繼承于它的子類或者使用系統(tǒng)預(yù)定義的子類。

NSOperation
把邏輯代碼寫在NSOperation中,就是把邏輯代碼添加一層殼,執(zhí)行NSOperation就是間接的執(zhí)行邏輯代碼。
- 用來(lái)定義操作對(duì)象的基礎(chǔ)(抽象)類。處理并發(fā)任務(wù)時(shí),具體子類通常要重寫main、isConcurrent、isExecuting 、isFinished方法。
- Operation默認(rèn)都是
串行操作(FIFO),默認(rèn)情況下Operation并不額外創(chuàng)建線程。
- 啟動(dòng)一個(gè)Operation任務(wù)
如果希望擁有更多的控制權(quán),以及在一個(gè)操作中可以執(zhí)行異步任務(wù),那么就重寫 start 方法。
如果重寫 start 方法,你必須手動(dòng)管理操作的狀態(tài)。 為了讓操作隊(duì)列能夠捕獲到操作的改變,需要將狀態(tài)的屬性以配合 KVO 的方式進(jìn)行實(shí)現(xiàn)。
- (void)start;
- 取消一個(gè)Operation
如果你在main方法中沒(méi)有對(duì)cancel進(jìn)行任何處理的話,發(fā)送cancel消息是沒(méi)有任何效果的。為了讓Operation響應(yīng)cancel消息,那么你就要在main方法中一些適當(dāng)?shù)牡胤绞謩?dòng)的判斷isCancelled屬性,如果返回YES的話,應(yīng)釋放相關(guān)資源并立刻停止繼續(xù)執(zhí)行。
@property (readonly, getter=isCancelled) BOOL cancelled;
- (void)cancel;
- 用來(lái)執(zhí)行你所想要執(zhí)行的任務(wù)
可以通過(guò)重寫 main 方法 來(lái)定義自己的 operations,開(kāi)發(fā)者不需要管理一些狀態(tài)屬性(例如 isExecuting 和 isFinished),當(dāng) main 方法返回的時(shí)候,這個(gè) operation 就結(jié)束了。
- (void)main;
- 判斷Operation是否是可并發(fā)的
@property (readonly, getter=isConcurrent) BOOL concurrent; // To be deprecated; use and override 'asynchronous' below
@property (readonly, getter=isAsynchronous) BOOL asynchronous API_AVAILABLE(macos(10.8), ios(7.0), watchos(2.0), tvos(9.0));
- 查看、添加、刪除 操作依賴
@property (readonly, copy) NSArray<NSOperation *> *dependencies;
- (void)addDependency:(NSOperation *)op;
- (void)removeDependency:(NSOperation *)op;
下面就是intermediateOperation操作必須等到operation1、operation2 完成后才能執(zhí)行。
[intermediateOperation addDependency:operation1];
[intermediateOperation addDependency:operation2];
- Operation執(zhí)行完成時(shí)自動(dòng)執(zhí)行completionBlock。可以在此進(jìn)行一些完成的處理。
每個(gè)Operation都可以設(shè)置一個(gè)completionBlock。
@property (nullable, copy) void (^completionBlock)(void) API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
- 線程優(yōu)先級(jí)
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
@property NSOperationQueuePriority queuePriority;
- 可以通過(guò)KVO監(jiān)聽(tīng)Operation的一下?tīng)顟B(tài)改變的Key
isCancelled
isConcurrent
isExecuting
isFinished
isReady
dependencies
queuePriority
completionBlock
NSInvocationOperation
- 初始化:
- (nullable instancetype)initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg;
- (instancetype)initWithInvocation:(NSInvocation *)inv;
- 變量
@property (readonly, retain) NSInvocation *invocation;
//個(gè)Operation完成后返回結(jié)果
@property (nullable, readonly, retain) id result;
NSBlockOperation
- 初始化:
+ (instancetype)blockOperationWithBlock:(void (^)(void))block;
- (void)addExecutionBlock:(void (^)(void))block;
@property (readonly, copy) NSArray<void (^)(void)> *executionBlocks;
Operation操作流程
一些公共方法:
-(void)operationComplete {
NSLog(@"All task finished.");
}
-(void)logOperation:(NSOperation *)op keyPathes:(NSArray *)keyPathes {
[keyPathes enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"%@ %@ = %@",op.name,obj,[[op valueForKey:obj] boolValue]?@"YES":@"NO");
}];
}
-(void)addObserverForOperation:(id)op keyPathes:(NSArray *)keyPathes {
[keyPathes enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[op addObserver:self forKeyPath:obj options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL];
}];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([object isKindOfClass:[NSOperation class]]) {
NSLog(@"observeValueForKeyPath:%@---%@---%@",[object name],keyPath,change);
}
}
- 先執(zhí)行start,后執(zhí)行cancel
-(void)operationKeysChange{
TestBlockOperation * op = [TestBlockOperation blockOperationWithBlock:^{
NSLog(@"enter op");
[NSThread sleepForTimeInterval:3];
NSLog(@"leave op");
}];
op.name = @"op";
NSArray * keyPathes = @[@"isReady",@"isCancelled",@"isExecuting",@"isFinished"];
[self logOperation:op keyPathes:keyPathes];
[self addObserverForOperation:op keyPathes:keyPathes];
op.completionBlock = ^{
[self operationComplete];
};
[op start];
[op cancel];
}
初始狀態(tài)下,ready為YES,其他均為NO。
當(dāng)我們調(diào)用 -start 后,執(zhí)行 -main 之前 isExecuting 屬性從NO被置為YES。
調(diào)用 -main 之后開(kāi)始執(zhí)行提交到Operation中的任務(wù)。
任務(wù)完成后 isExecuting 屬性從YES被置為NO,isFinished 屬性從NO被置為YES。
op isReady = YES
op isCancelled = NO
op isExecuting = NO
op isFinished = NO
op before start
observeValueForKeyPath:op---isExecuting---{kind = 1;new = 1;old = 0;}
op before main
enter bp1
leave bp1
/*這里執(zhí)行業(yè)務(wù)代碼,完成后才進(jìn)行下面的操作*/
op after main
All task finished.
observeValueForKeyPath:op---isExecuting---{kind = 1;new = 0;old = 1;}
observeValueForKeyPath:op---isFinished---{kind = 1;new = 1;old = 0;}
op after start
op before cancel
op after cancel
- 先執(zhí)行cancel,后執(zhí)行start
-(void)operationKeysChange{
TestBlockOperation * op = [TestBlockOperation blockOperationWithBlock:^{
NSLog(@"enter op");
[NSThread sleepForTimeInterval:3];
NSLog(@"leave op");
}];
op.name = @"op";
NSArray * keyPathes = @[@"isReady",@"isCancelled",@"isExecuting",@"isFinished"];
[self logOperation:op keyPathes:keyPathes];
[self addObserverForOperation:op keyPathes:keyPathes];
op.completionBlock = ^{
[self operationComplete];
};
[op cancel];
[op start];
}
先調(diào)用 -start ,后調(diào)用 -cancel ,isCancelled 屬性從NO被置為YES,isReady 屬性無(wú)論什么狀態(tài)都會(huì)被置為YES。
先調(diào)用 -start ,后調(diào)用 -cancel ,會(huì)將 isFinished 屬性從NO被置為YES,然后并不調(diào)用 -main 方法。
op isReady = YES
op isCancelled = NO
op isExecuting = NO
op isFinished = NO
op before cancel
observeValueForKeyPath:op---isCancelled---{kind = 1;new = 1;old = 0;}
observeValueForKeyPath:op---isReady---{kind = 1;new = 1;old = 1;}
op after cancel
op before start
All task finished.
observeValueForKeyPath:op---isFinished---{kind = 1;new = 1;old = 0;}
op after start
- NSOperationQueue 但是沒(méi)有依賴
-(void) operationKeysChange {
TestBlockOperation * bp1 = [TestBlockOperation blockOperationWithBlock:^{
NSLog(@"enter bp1");
[NSThread sleepForTimeInterval:3];
NSLog(@"leave bp1");
}];
bp1.name = @"bp1";
bp1.completionBlock = ^{
NSLog(@"bp1 complete");
};
TestBlockOperation * bp2 = [TestBlockOperation blockOperationWithBlock:^{
NSLog(@"enter bp2");
[NSThread sleepForTimeInterval:3];
NSLog(@"leave bp2");
}];
bp2.name = @"bp2";
bp2.completionBlock = ^{
NSLog(@"bp2 complete");
};
NSArray * keyPathes = @[@"isReady",@"isCancelled",@"isExecuting",@"isFinished"];
[self logOperation:bp1 keyPathes:keyPathes];
[self logOperation:bp2 keyPathes:keyPathes];
[self addObserverForOperation:bp1 keyPathes:keyPathes];
[self addObserverForOperation:bp2 keyPathes:keyPathes];
NSOperationQueue * q = [NSOperationQueue new];
[q addOperation:bp1];
[q addOperation:bp2];
}
NSOperationQueue 執(zhí)行順序和NSOperation一樣
bp1 isReady = YES
bp1 isCancelled = NO
bp1 isExecuting = NO
bp1 isFinished = NO
bp2 isReady = YES
bp2 isCancelled = NO
bp2 isExecuting = NO
bp2 isFinished = NO
bp1 before start
bp2 before start
observeValueForKeyPath:bp1---isExecuting---{kind = 1;new = 1;old = 0;}
observeValueForKeyPath:bp2---isExecuting---{kind = 1;new = 1;old = 0;}
bp1 before main
bp2 before main
enter bp1
enter bp2
leave bp1
leave bp2
bp2 after main
bp1 after main
bp2 complete
bp1 complete
observeValueForKeyPath:bp1---isExecuting---{kind = 1;new = 0;old = 1;}
observeValueForKeyPath:bp2---isExecuting---{kind = 1;new = 0;old = 1;}
observeValueForKeyPath:bp1---isFinished---{kind = 1;new = 1;old = 0;}
observeValueForKeyPath:bp2---isFinished---{kind = 1;new = 1;old = 0;}
bp1 after start
bp2 after start
- NSOperationQueue 但是有依賴
-(void) operationKeysChange {
TestBlockOperation * bp1 = [TestBlockOperation blockOperationWithBlock:^{
NSLog(@"enter bp1");
[NSThread sleepForTimeInterval:3];
NSLog(@"leave bp1");
}];
bp1.name = @"bp1";
bp1.completionBlock = ^{
NSLog(@"bp1 complete");
};
TestBlockOperation * bp2 = [TestBlockOperation blockOperationWithBlock:^{
NSLog(@"enter bp2");
[NSThread sleepForTimeInterval:3];
NSLog(@"leave bp2");
}];
bp2.name = @"bp2";
bp2.completionBlock = ^{
NSLog(@"bp2 complete");
};
NSArray * keyPathes = @[@"isReady",@"isCancelled",@"isExecuting",@"isFinished"];
[self logOperation:bp1 keyPathes:keyPathes];
[self logOperation:bp2 keyPathes:keyPathes];
[self addObserverForOperation:bp1 keyPathes:keyPathes];
[self addObserverForOperation:bp2 keyPathes:keyPathes];
NSOperationQueue * q = [NSOperationQueue new];
[bp1 addDependency:bp2];
[q addOperation:bp1];
[q addOperation:bp2];
}
當(dāng)為bp1添加bp2作為依賴以后,bp1的 isReady 屬性從YES置為NO。
由于bp2是bp1的依賴,所以優(yōu)先執(zhí)行bp2。
在bp2中任務(wù)完成之后,-main 方法調(diào)用結(jié)束之后, -start 方法調(diào)用結(jié)束之前,bp1調(diào)用 -start 并將 isReady 屬性置為YES。
bp1 isReady = YES
bp1 isCancelled = NO
bp1 isExecuting = NO
bp1 isFinished = NO
bp2 isReady = YES
bp2 isCancelled = NO
bp2 isExecuting = NO
bp2 isFinished = NO
bp1 before addDependency:
observeValueForKeyPath:bp1---isReady---{kind = 1;new = 0;old = 1;}
bp1 after addDependency:
bp2 before start
observeValueForKeyPath:bp2---isExecuting---{kind = 1;new = 1;old = 0;}
bp2 before main
enter bp2
leave bp2
bp2 after main
bp1 before start
observeValueForKeyPath:bp1---isReady---{kind = 1;new = 1;old = 0;}
observeValueForKeyPath:bp1---isExecuting---{kind = 1;new = 1;old = 0;}
bp2 complete
observeValueForKeyPath:bp2---isExecuting---{kind = 1;new = 0;old = 1;}
bp1 before main
enter bp1
observeValueForKeyPath:bp2---isFinished---{kind = 1;new = 1;old = 0;}
bp2 after start
leave bp1
bp1 after main
bp1 complete
observeValueForKeyPath:bp1---isExecuting---{kind = 1;new = 0;old = 1;}
observeValueForKeyPath:bp1---isFinished---{kind = 1;new = 1;old = 0;}
bp1 after start

- 通過(guò)上面的流程圖自定義實(shí)現(xiàn)簡(jiǎn)單的并發(fā)操作
@interface SAMOperation : NSOperation
@end
@implementation SAMOperation{
BOOL isFinished;//監(jiān)聽(tīng)是否執(zhí)行結(jié)束
BOOL isExecuting;//監(jiān)聽(tīng)是否正在執(zhí)行
}
/*1.自定義初始化方法*/
-(instancetype)init{
if (self == [super init]) {
isExecuting = NO;
isFinished = NO;
}
return self;
}
-(void)start{
//如果被取消了就直接返回結(jié)果,不會(huì)執(zhí)行main方法
if ([self isCancelled]) {
[self willChangeValueForKey:@"isFinished"];
isFinished = NO;
[self didChangeValueForKey:@"isFinished"];
return;
}
//沒(méi)有被取消,使用獨(dú)立線程執(zhí)行main方法中的操作
[self willChangeValueForKey:@"isExecuting"];
[NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
isExecuting = YES;
[self didChangeValueForKey:@"isExecuting"];
}
/*2.自定義輔助方法*/
-(void)main{
@autoreleasepool{//使用獨(dú)立的內(nèi)存釋放池,不然會(huì)內(nèi)存泄漏
@try {
if (![self isCancelled]) {
NSLog(@"Begin%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
NSLog(@"End%@",[NSThread currentThread]);
//任務(wù)結(jié)束,修改狀態(tài)值
[self willChangeValueForKey:@"isFinished"];
[self willChangeValueForKey:@"isExecuting"];
isExecuting = NO;
isFinished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
}
} @catch (NSException *exception) {
} @finally {
}
}
}
-(BOOL)isConcurrent{
return YES;
}
-(BOOL)isExecuting{
return isExecuting;
}
-(BOOL)isFinished{
return isFinished;
}
@end
使用:
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"begin func");
SAMOperation *op1 = [SAMOperation new];
[op1 start];
NSLog(@"end func");
}
NSOperationQueue
- 兩種不同類型的隊(duì)列:主隊(duì)列和自定義隊(duì)列。主隊(duì)列運(yùn)行在主線程之上,而自定義隊(duì)列在后臺(tái)執(zhí)行。在兩種類型中,這些隊(duì)列所處理的任務(wù)都使用 NSOperation 的子類來(lái)表述。
- NSOperationQueue是一個(gè)Operation執(zhí)行隊(duì)列,你可以將任何你想要執(zhí)行的Operation添加到Operation Queue中,以在隊(duì)列中執(zhí)行。
- NSOperationQueue 可以動(dòng)態(tài)的創(chuàng)建多個(gè)線程來(lái)完成相應(yīng)Operation,總線程數(shù)量通過(guò)maxConcurrentOperationCount屬性來(lái)控制。
- 當(dāng)操作添加到隊(duì)列中,它會(huì)待在隊(duì)列中,直到被顯式取消或者執(zhí)行完為止。
- 創(chuàng)建隊(duì)列
// 主隊(duì)列隊(duì)列
NSOperationQueue *queue = [NSOperationQueue mainQueue];
// 自定義隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
- 設(shè)置最大并發(fā)數(shù)
maxConcurrentOperationCount 為1時(shí),隊(duì)列為串行隊(duì)列。只能串行執(zhí)行。
maxConcurrentOperationCount 默認(rèn)情況下為-1,表示不進(jìn)行限制,可進(jìn)行并發(fā)執(zhí)行。
maxConcurrentOperationCount 大于1時(shí),隊(duì)列為并發(fā)隊(duì)列。操作并發(fā)執(zhí)行,當(dāng)然這個(gè)值不應(yīng)超過(guò)系統(tǒng)限制,即使自己設(shè)置一個(gè)很大的值,系統(tǒng)也會(huì)自動(dòng)調(diào)整為 min{自己設(shè)定的值,系統(tǒng)設(shè)定的默認(rèn)最大值}。
@property NSInteger maxConcurrentOperationCount;
- 取消操作
- (void)cancelAllOperations;
NSOperationQueue 就相當(dāng)于管道,Operation以FIFO的形式通過(guò)管道,maxConcurrentOperationCount 就是管道數(shù)量。下面的demo就是限制總管道數(shù)量為1,也就是所有的Operation必須以FIFO形式通過(guò)管道,也就是串行。
- (void)demo {
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"operation1-%@",[NSThread currentThread]);
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"operation2-%@",[NSThread currentThread]);
}];
operation2.completionBlock = ^{NSLog(@"operation2-completionBlock-%@",[NSThread currentThread]);};
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"operation3-%@",[NSThread currentThread]);
}];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;
[queue addOperation:operation1];
[queue addOperation:operation2];
[queue addOperation:operation3];
[queue waitUntilAllOperationsAreFinished];
}
執(zhí)行結(jié)果:從結(jié)果可以看出,maxConcurrentOperationCount = 1 相當(dāng)于在addOperation的時(shí)候就設(shè)置了操作之間的依賴。
operation1-<NSThread: 0x604000270100>{number = 3, name = (null)}
operation2-<NSThread: 0x604000270540>{number = 4, name = (null)}
operation2-completionBlock-<NSThread: 0x604000270100>{number = 3, name = (null)}
operation3-<NSThread: 0x604000270540>{number = 4, name = (null)}
設(shè)置queue.maxConcurrentOperationCount = 2;的執(zhí)行結(jié)果
operation2-<NSThread: 0x60400027be80>{number = 4, name = (null)}
operation1-<NSThread: 0x600000474140>{number = 3, name = (null)}
operation3-<NSThread: 0x60400027be80>{number = 4, name = (null)}
operation2-completionBlock-<NSThread: 0x600000474340>{number = 5, name = (null)}
- 添加Operation的三種方式
- (void)addOperation:(NSOperation *)op;
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
- (void)addOperationWithBlock:(void (^)(void))block API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
- 簡(jiǎn)單的自定義Operation
@interface SAMOperation : NSOperation
@end
@implementation SAMOperation
-(void)main{
@autoreleasepool{
@try {
if (![self isCancelled]) {
NSLog(@"Begin");
[NSThread sleepForTimeInterval:1.0];
NSLog(@"End");
}
} @catch (NSException *exception) {
} @finally {
}
}
}
@end
使用:
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"begin func");
SAMOperation *op1 = [SAMOperation new];
NSOperationQueue *queue = [NSOperationQueue new];
[queue addOperation:op1];
//[queue waitUntilAllOperationsAreFinished];
NSLog(@"end func");
}
打印
begin func
end func
Begin
End
//把[queue waitUntilAllOperationsAreFinished];注釋打開(kāi)的打印
begin func
Begin
End
end func
線程安全和 線程同步
@interface ViewController ()
@property (nonatomic, assign) NSInteger ticketSurplusCount;
@property (nonatomic, strong)NSLock *lock;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.ticketSurplusCount = 50;
self.lock = [[NSLock alloc] init];
[self initTicketStatusNotSave];
}
/**
* 非線程安全:不使用 semaphore
* 初始化火車票數(shù)量、賣票窗口(非線程安全)、并開(kāi)始賣票
*/
- (void)initTicketStatusNotSave {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當(dāng)前線程
NSLog(@"semaphore---begin");
self.ticketSurplusCount = 50;
__weak typeof(self) weakSelf = self;
// queue1 代表北京火車票售賣窗口
NSOperationQueue *queueO1 = [[NSOperationQueue alloc]init];
queueO1.maxConcurrentOperationCount = 1;
[queueO1 addOperationWithBlock:^{
[weakSelf saleTicketNotSafe:@"北京"];
}];
// queue2 代表上海火車票售賣窗口
NSOperationQueue *queueO2 = [[NSOperationQueue alloc]init];
queueO2.maxConcurrentOperationCount = 1;
[queueO2 addOperationWithBlock:^{
[weakSelf saleTicketNotSafe:@"上海"];
}];
// queue2 代表上?;疖嚻笔圪u窗口
NSOperationQueue *queueO3 = [[NSOperationQueue alloc]init];
queueO3.maxConcurrentOperationCount = 1;
[queueO3 addOperationWithBlock:^{
[weakSelf saleTicketNotSafe:@"深圳"];
}];
}
/**
* 售賣火車票(非線程安全)
*/
- (void)saleTicketNotSafe:(NSString*)who{
while (1) {
[self.lock lock];
if (self.ticketSurplusCount > 0) { //如果還有票,繼續(xù)售賣
self.ticketSurplusCount--;
NSLog(@"%@", [NSString stringWithFormat:@"剩余票數(shù):%ld 窗口:%@====%@", (long)self.ticketSurplusCount,who, [NSThread currentThread]]);
//[NSThread sleepForTimeInterval:0.2];
[self.lock unlock];
} else { //如果已賣完,關(guān)閉售票窗口
NSLog(@"所有火車票均已售完");
[self.lock unlock];
break;
}
}
}
@end