你的NSOperation dealloc了么?并发数真的生效了么?

非常偶然的情况下发现自定义的NSOperation子类都没有被释放,运行完乖乖的躺在OperationQueue中,大为惊异之后开始了我对于NSOperation以及NSOperationQueue的一些学习。

NSOperation 是iOS开发中常用的一种并发编程方式,主要有两个常用的子类,NSInvocationOperation和NSBlockOperation,网上有大量的文章介绍,这里不再赘述。

但是我们很多时候这两个子类并不能完成我们的需求,这个时候我们就需要继承一下NSOperation来做一下封装,往上很多文章对此往往一笔带过,然后我们稍不留神就进坑了。。。

我们来看下到底有啥问题。
首先我创建了一个maxConcurrentOperationCount=1 的NSOperationQueue,然后定义了一发

NSOperation的子类:JDTestOperation

@implementation JDTestOperation
- (void)main
{
        NSLog(@"%@ start",self.opName);
        sleep(1);
        NSLog(@"%@ end",self.opName);
}
@end

生成若干个JDTestOperation对象加到Queue里,每个都会执行,并无异常。但是无一例外都不会被释放。。。这是为啥呢。。。然后就开始找资料,猛然发现Apple文档中这样一段话


额。。。我们需要生成isExecuting 和 isFinished 的相关通知,这样才方便Queue来管理内部的Operation。。。
由于我们并未做这些工作,所以Queue对里面的所有任务都发出了main消息,然后Queue并不知道他们的状态,也就一直把他们留在自己的容器里面了,so,内存泄露了。。。

那我们没有做这种工作,为啥内部的Operation也都一个一个运行了呢。。。(如果是并行队列的话,也会并行的运行,看上去并没啥异常)

通过实验发现,由于队列是串行队列,main函数里面又是一路跑到底的逻辑,所以Queue在执行任务的时候,一个operation的main/start跑完了,发现自己内部没有任务在跑,就会启动下一个任务,所以逻辑上并无异常。如果是并发队列的话,比如并发数为2,就会在两个线程上分别向俩operation发送main/start 消息,跑完了发现没有任务在执行(因为我们没有通过KVO设置状态),就会继续后面的operation。

我们把上面的main方法改成下面的样子:

- (void)main
{
        NSLog(@"%@ start",self.opName);
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
                //do something really important
                sleep(1);
                NSLog(@"%@ end",self.opName);
        });
}

跑起来你就会发现Operation跑的满天飞,哪还管你maxConcurrentOperationCount设置了多少,这是因为main内部的操作都跑到GCD global queue去了,这种写发下OperationQueue并不能对这个做有效的控制。

那么正确的写法是啥?如下:

@interface JDTestOperation()
@property(assign,nonatomic,getter= isExecuting)BOOLexecuting;
@property(assign,nonatomic,getter= isFinished)BOOLfinished;
@end
@implementation JDTestOperation
@synthesizeexecuting =_executing;
@synthesizefinished =_finished;
- (void)main
{
        [self setExecuting:YES];
        NSLog(@"%@ start",self.opName);
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
                //do something really important
                sleep(1);
                NSLog(@"%@ end",self.opName);
                [self setExecuting:NO];
                [self setFinished:YES];
        });
}
- (void)setFinished:(BOOL)finished
{
        [self willChangeValueForKey:@"isFinished"];
        _finished= finished;
        [self didChangeValueForKey:@"isFinished"];
}
- (void)setExecuting:(BOOL)executing
{
        [self willChangeValueForKey:@"isExecuting"];
        _executing= executing;
        [self didChangeValueForKey:@"isExecuting"];
}

这下子operation可以正确释放了,并发数也能有效控制住了,一切都回到正确的轨道了。。。

参考资料

https://developer.apple.com/reference/foundation/operation

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

推荐阅读更多精彩内容