非常偶然的情况下发现自定义的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可以正确释放了,并发数也能有效控制住了,一切都回到正确的轨道了。。。