概述
iOS并发编程中,把每个并发任务定义为一个Operation。NSOperation是一个抽象类,无法直接使用,它只定义了Operation的一些基本方法。我们需要创建一个继承于它的子类或者使用系统预定义的子类。
NSOperation
把逻辑代码写在NSOperation中,就是把逻辑代码添加一层壳,执行NSOperation就是间接的执行逻辑代码。
- 用来定义操作对象的基础(抽象)类。处理并发任务时,具体子类通常要重写main、isConcurrent、isExecuting 、isFinished方法。
- Operation默认都是
串行操作(FIFO)
,默认情况下Operation并不额外创建线程。
- 启动一个Operation任务
如果希望拥有更多的控制权,以及在一个操作中可以执行异步任务,那么就重写 start 方法。
如果重写 start 方法,你必须手动管理操作的状态。 为了让操作队列能够捕获到操作的改变,需要将状态的属性以配合 KVO 的方式进行实现。
- (void)start;
- 取消一个Operation
如果你在main方法中没有对cancel进行任何处理的话,发送cancel消息是没有任何效果的。为了让Operation响应cancel消息,那么你就要在main方法中一些适当的地方手动的判断isCancelled属性,如果返回YES的话,应释放相关资源并立刻停止继续执行。
@property (readonly, getter=isCancelled) BOOL cancelled;
- (void)cancel;
- 用来执行你所想要执行的任务
可以通过重写 main 方法 来定义自己的 operations,开发者不需要管理一些状态属性(例如 isExecuting 和 isFinished),当 main 方法返回的时候,这个 operation 就结束了。
- (void)main;
- 判断Operation是否是可并发的
@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
完成后才能执行。
[intermediateOperation addDependency:operation1];
[intermediateOperation addDependency:operation2];
- Operation执行完成时自动执行completionBlock。可以在此进行一些完成的处理。
每个Operation都可以设置一个completionBlock。
@property (nullable, copy) void (^completionBlock)(void) API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
- 线程优先级
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
@property NSOperationQueuePriority queuePriority;
- 可以通过KVO监听Operation的一下状态改变的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;
//个Operation完成后返回结果
@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);
}
}
- 先执行start,后执行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];
}
初始状态下,ready为YES,其他均为NO。
当我们调用 -start 后,执行 -main 之前 isExecuting 属性从NO被置为YES。
调用 -main 之后开始执行提交到Operation中的任务。
任务完成后 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
/*这里执行业务代码,完成后才进行下面的操作*/
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
- 先执行cancel,后执行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];
}
先调用 -start ,后调用 -cancel ,isCancelled 属性从NO被置为YES,isReady 属性无论什么状态都会被置为YES。
先调用 -start ,后调用 -cancel ,会将 isFinished 属性从NO被置为YES,然后并不调用 -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 但是没有依赖
-(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 执行顺序和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];
}
当为bp1添加bp2作为依赖以后,bp1的 isReady 属性从YES置为NO。
由于bp2是bp1的依赖,所以优先执行bp2。
在bp2中任务完成之后,-main 方法调用结束之后, -start 方法调用结束之前,bp1调用 -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
- 通过上面的流程图自定义实现简单的并发操作
@interface SAMOperation : NSOperation
@end
@implementation SAMOperation{
BOOL isFinished;//监听是否执行结束
BOOL isExecuting;//监听是否正在执行
}
/*1.自定义初始化方法*/
-(instancetype)init{
if (self == [super init]) {
isExecuting = NO;
isFinished = NO;
}
return self;
}
-(void)start{
//如果被取消了就直接返回结果,不会执行main方法
if ([self isCancelled]) {
[self willChangeValueForKey:@"isFinished"];
isFinished = NO;
[self didChangeValueForKey:@"isFinished"];
return;
}
//没有被取消,使用独立线程执行main方法中的操作
[self willChangeValueForKey:@"isExecuting"];
[NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
isExecuting = YES;
[self didChangeValueForKey:@"isExecuting"];
}
/*2.自定义辅助方法*/
-(void)main{
@autoreleasepool{//使用独立的内存释放池,不然会内存泄漏
@try {
if (![self isCancelled]) {
NSLog(@"Begin%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
NSLog(@"End%@",[NSThread currentThread]);
//任务结束,修改状态值
[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
- 两种不同类型的队列:主队列和自定义队列。主队列运行在主线程之上,而自定义队列在后台执行。在两种类型中,这些队列所处理的任务都使用 NSOperation 的子类来表述。
- NSOperationQueue是一个Operation执行队列,你可以将任何你想要执行的Operation添加到Operation Queue中,以在队列中执行。
- NSOperationQueue 可以动态的创建多个线程来完成相应Operation,总线程数量通过maxConcurrentOperationCount属性来控制。
- 当操作添加到队列中,它会待在队列中,直到被显式取消或者执行完为止。
- 创建队列
// 主队列队列
NSOperationQueue *queue = [NSOperationQueue mainQueue];
// 自定义队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
- 设置最大并发数
maxConcurrentOperationCount 为1时,队列为串行队列。只能串行执行。
maxConcurrentOperationCount 默认情况下为-1,表示不进行限制,可进行并发执行。
maxConcurrentOperationCount 大于1时,队列为并发队列。操作并发执行,当然这个值不应超过系统限制,即使自己设置一个很大的值,系统也会自动调整为 min{自己设定的值,系统设定的默认最大值}。
@property NSInteger maxConcurrentOperationCount;
- 取消操作
- (void)cancelAllOperations;
NSOperationQueue 就相当于管道,Operation以FIFO的形式通过管道,maxConcurrentOperationCount 就是管道数量。下面的demo就是限制总管道数量为1,也就是所有的Operation必须以FIFO形式通过管道,也就是串行。
- (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];
}
执行结果:从结果可以看出,maxConcurrentOperationCount = 1 相当于在addOperation的时候就设置了操作之间的依赖。
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)}
设置queue.maxConcurrentOperationCount = 2;的执行结果
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));
- 简单的自定义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];注释打开的打印
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
* 初始化火车票数量、卖票窗口(非线程安全)、并开始卖票
*/
- (void)initTicketStatusNotSave {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
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 代表上海火车票售卖窗口
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) { //如果还有票,继续售卖
self.ticketSurplusCount--;
NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@====%@", (long)self.ticketSurplusCount,who, [NSThread currentThread]]);
//[NSThread sleepForTimeInterval:0.2];
[self.lock unlock];
} else { //如果已卖完,关闭售票窗口
NSLog(@"所有火车票均已售完");
[self.lock unlock];
break;
}
}
}
@end