非常偶然的情況下發現自定義的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可以正確釋放了,并發數也能有效控制住了,一切都回到正確的軌道了。。。