Demo地址:https://github.com/fanbaoyuan/Custom-NSOperation
需求背景:
最近在做项目的时候遇到一个需求:在上传图片之前,先压缩图片,实现边压缩边上传,同时最大上传的数量不能超过四个,且支持取消功能。上面的需求概括起来就是,我需要先压缩图片,图片压缩后在上传图片,且同时在上传的图片数量不能超过四个。 之前使用的是信号量来实现最大上传数量这个需求,现在加上压缩后在上传,且支持取消,我直接想到的是使用NSOperation来实现,支持最大并发量和取消等功能。大概思路就是,用一个compressOpertion来压缩图片,添加到compressQueue中,compressOpertion中的操作结束后,再用一个uploadOperation实现上传的操作,并加入到uploadQueue中,且uploadQueue的maxConcurrentOperationCount = 4; 这里面的一个难点就是,uploadOperation里面实现的上传操作是异步并行的,只有等上传回调后才能算当前uploadOperation结束,而不是说operation里面的代码执行完后就结束(串行)。这就引出了这边文章的主题:NSOperation实现异步操作。
关于NSOperation的介绍以及他两个子类的用法这里就不介绍,下面主要说明下NSOperation自定义的用法。NSOperation的自定义有两种:
1、同步操作,在main方法中实现对应的操作,当main方法走完后,操作也就结束,operation会自动从queue中删除,这种方法实现的是串行操作,当然如果想这样的实现并行操作的话也可以,在main方法中加入信号量等卡住当前的子线程,当main方法中的异步操作结束后在打开;
2、并发操作、此时不需要实现main方法,但是需要实现start方法,由于是异步操作,此时还需要主动维护isExecuting、isFinished这个状态(同步操作中就不需要维护,queue自己会管理operation的执行状态),并且实现对应的kvo,当前isExecuting、isFinished值改变时,queue会监听其变化,当isFinished = YES时,并会将operation从queue中删除。所以当操作中的上传回调时,即可改变finished的值,这时当前的operation才算真的结束。
下面通过CustomOperation : NSOperation类的实现来介绍:
在类中添加bees_executing、bees_executing两个属性,用来管理自定义operation执行任务的状态
@interface CustomOperation ()
/// 是否正在进行
@property (nonatomic, assign) BOOL bees_executing;
/// 是否完成
@property (nonatomic, assign) BOOL bees_executing;
@end
实现start方法,根据不同的状态进行不同的处理
- (void)start{
if (self.isCancelled) {
// 若当前操作为取消,则结束操作,且要修改isExecuting和isFinished的值,通过kvo的方式告诉对应的监听者其值的改变
NSLog(@"%@ cancel %@", self.taskName,self);
[self completeOperation];
}else{
// 正在执行操作
self.bees_executing = YES;
// 通过代理,在外部实现对应的异步操作
if(self.delegate&& [self.delegaterespondsToSelector:@selector(startOperation:)]) {
[self.delegate startOperation:self];
}
}
}
当异步的上传回调的时候,需要改变operation的状态,然后通知对应的监听对象,如queue,将operation从queue中删除
/// 结束当前操作,改变对应的状态
- (void)completeOperation {
self.bees_executing = NO;
self.bees_finished = YES;
}
当调用queue 的cancelAllOperations取消方法时,会调用遍历operation数组,调用每个[operation cancel], 所以自己管理状态时,一定要重写cancel方法,然后改变operation的状态
// 一定要重写cancel方法,结束状态
- (void)cancel{
[supercancel];
// 取消后一定要调用完成,删除queue中的operation
[self completeOperation];
}
实现对应kvo方法,发送父类对应属性状态改变的kvo通知
// setter 修改自己状态的同时,发送父类对应属性状态改变的kvo通知
- (void)setBees_executing:(BOOL)bees_executing {
[self willChangeValueForKey:@"isExecuting"];
_bees_executing= bees_executing;
[self didChangeValueForKey:@"isExecuting"];
}
- (void)setBees_finished:(BOOL)bees_finished {
[self willChangeValueForKey:@"isFinished"];
_bees_finished= bees_finished;
[self didChangeValueForKey:@"isFinished"];
}
// 父类返回自己维护的对应的状态
- (BOOL)isExecuting {
return self.bees_executing;
}
- (BOOL)isFinished {
return self.bees_finished;
}
方法调用,设置最大并发数
- (void)viewDidLoad {
[super viewDidLoad];
self.compressQueue = [[NSOperationQueue alloc]init];
// 设置最大压缩操作为1
self.compressQueue.maxConcurrentOperationCount = 1;
self.uploadQueue = [[NSOperationQueue alloc]init];
// 设置上传最大操作为4
self.uploadQueue.maxConcurrentOperationCount = 4;
}
开始边压缩边上传,压缩最大数为1, 上传最大数为4
- (IBAction)didClickStartButton:(UIButton *)sender {
NSIntegertaskCount =6;
for(NSIntegerindex =0; index < taskCount; index++) {
NSString*name = [NSStringstringWithFormat:@"task_%zd",index];
// 创建压缩操作,压缩是串行操作,代码j走完就算结束,使用NSBlockOperation
NSBlockOperation*compressOperation = [NSBlockOperationblockOperationWithBlock:^{
NSLog(@"%@ 压缩中...", name);
// 模拟压缩延时
[NSThread sleepForTimeInterval:2];
NSLog(@"%@ 结束压缩", name);
}];
// 创建上传操作
CustomOperation*uploadOperation = [[CustomOperationalloc]initWithTaskName:name];
// 设置上传操作代理,完成对应的上传任务
uploadOperation.delegate=self;
// 添加依赖,当压缩完成后在执行上传任务
[uploadOperationaddDependency:compressOperation];
if(!compressOperation.isCancelled) {
// 如果压缩没有取消,添加到压缩操作到压缩队列
[self.compressQueueaddOperation:compressOperation];
if(!uploadOperation.isCancelled) {
// 如果压缩没有取消,且上传任务没有取消,将上传操作添加到上传队列中
[self.uploadQueueaddOperation:uploadOperation];
}
}
}
}
点击取消方法,分别调用两个队列的取消
- (IBAction)didClickCancelButton:(UIButton *)sender {
NSLog(@"取消任务");
// 取消压缩队列中的内容
[self.compressQueue cancelAllOperations];
// 取消上传队列中的内容,CustomOperation 需要重写cancel方法,修改状态,不然operation不会从queue中移除,会一直存在,这点在网上搜了一圈都没有写到,最后是看sdwebimage中的SDAsyncBlockOperation也重写cancel,在cancel改变operation的状态才知道;阅读优秀第三方源码还是很有用
[self.uploadQueue cancelAllOperations];
// 最后在调用上传的取消 等等
}
代理实现,在外部实现上传操作
#pragma - mark CustomOperationDelegate
// 开始上传,完成后主动修改operation的状态
- (void)startOperation:(CustomOperation*)operation {
// 模拟上传任务
// __weak typeof(self)weakSelf = self;
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"%@ 上传中...%@",operation.taskName,[NSThreadcurrentThread]);
// 延时
[NSThread sleepForTimeInterval:10];
// 主线程回调
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"%@ 上传完成",operation.taskName);
// 上传完成后一定要改变当前operation的状态,
[operation completeOperation];
});
});
}
调用start 方法log:
从log中可以看到,上传完成后调用了dealloc方法,因为我们上传完成后调用了 [operation completeOperation];改变operation的状态
当压缩到task_5时候点击取消log
可以看到task_5压缩后,并没有继续上传task_5的内容,且所有operation都dealloc
至此全部完成。