自定义NSOperation实现异步操作

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

至此全部完成。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容