自定义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

至此全部完成。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容