你的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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容