NSURLSession 對(duì) delegate 強(qiáng)引用造成的內(nèi)存泄露

原文鏈接:http://lm1024.xyz/2020/05/31/NSURLSession-memory-leak/

場景

在使用 profile查找項(xiàng)目中的內(nèi)存泄露問題時(shí)發(fā)現(xiàn)在上傳文件的模塊中總是報(bào)內(nèi)存泄露。自己封裝的上傳器工具類也沒走 dealloc。Profile 只提示了我開始上傳的方法里面存在泄露。

內(nèi)存泄露的情況大概有

  • block 循環(huán)引用
  • delegate 強(qiáng)引用
  • 自定義對(duì)象之間互相持有
  • 系統(tǒng)對(duì)象和自定義對(duì)象之間互相持有
  • CoreFoundation 框架對(duì)象沒有手動(dòng) Realese 掉

分析問題

經(jīng)初步分析可能會(huì)有以下兩個(gè)情況會(huì)導(dǎo)致 上傳工具類不被釋放

  • 上傳完成之后上傳管理器中的文件 URL <-->上傳工具類之間的映射 map 沒有將上傳工具對(duì)象從 map 里面移除掉。
  • block 造成循環(huán)引用

按照上述的思路修改之后再次 profile 文件上傳模塊發(fā)現(xiàn)熟悉的內(nèi)存泄露還是出現(xiàn)了??吹竭@里笑容漸漸消失了。確定了按上述思路修改沒有遺漏的地方后我意識(shí)到可能是上傳工具類內(nèi)部處問題。

#pragma Setter / Getter
- (NSURLSession *)uploadSession
{
    if (_uploadSession == nil)
    {
        NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
        _uploadSession = [NSURLSession sessionWithConfiguration:config
                                                       delegate:self delegateQueue:self.runQueue];
    }
    return _uploadSession;
}

檢查到上面代碼的時(shí)候發(fā)現(xiàn)一個(gè)可疑點(diǎn)[NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:self.runQueue];
在給NSURLSession 設(shè)置 delegate 的時(shí)候,NSURLSession 會(huì)不會(huì)是對(duì) delegate 進(jìn)行了強(qiáng)引用?帶著疑問去查了下文檔

  • configuration
    A configuration object that specifies certain behaviors, such as caching policies, timeouts, proxies, pipelining, TLS versions to support, cookie policies, and credential storage.
    See NSURLSessionConfiguration for more information.
  • delegate
    A session delegate object that handles requests for authentication and other session-related events.
    This delegate object is responsible for handling authentication challenges, for making caching decisions, and for handling other session-related events. If nil, the class should be used only with methods that take completion handlers.
    Important
    The session object keeps a strong reference to the delegate until your app exits or explicitly invalidates the session. If you do not invalidate the session by calling the invalidateAndCancel or finishTasksAndInvalidate method, your app leaks memory until it exits.
  • queue
    An operation queue for scheduling the delegate calls and completion handlers. The queue should be a serial queue, in order to ensure the correct ordering of callbacks. If nil, the session creates a serial operation queue for performing all delegate method calls and completion handler calls.

果然,沒有猜錯(cuò), NSURLSession對(duì)象強(qiáng)引用了我的上傳工具類并且 NSURLSession對(duì)象又是上傳工具類的一個(gè)屬性。這樣就形成了循環(huán)引用,這種情況屬于

  • 系統(tǒng)對(duì)象強(qiáng)制持有 delegate 類

解決方案

破除循環(huán)引用的方法文檔中也給出來了在下載完成時(shí)調(diào)用- (void)finishTasksAndInvalidate;, 取消當(dāng)前會(huì)話中task任務(wù)并且是當(dāng)前會(huì)話失效 調(diào)用- (void)invalidateAndCancel;。

  - (void)uploadData:(NSData *)data  completed:(WLFileUploadCompleted) completed
{
    self.uploadTask =  [self.uploadSession uploadTaskWithRequest:self.request
                                                        fromData:data
                                               completionHandler:completed];
    [self.uploadTask addObserver:self
                      forKeyPath:@"state"
                         options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
                         context:@"state"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    NSString * cntext = (__bridge NSString *)context;
    if ([cntext isEqualToString:@"state"] && [keyPath isEqualToString:@"state"])
    {
        NSURLSessionTaskState state = [change[NSKeyValueChangeNewKey] integerValue];
        NSLog(@"NEW NSURLSessionTaskState = %ld ",(long)state);
        if (state == NSURLSessionTaskStateCanceling ||
            state == NSURLSessionTaskStateCompleted)
        {
            [self releaseCallback];
        }
    }
}
- (void)releaseCallback
{
    self.progress = nil;
    [self.uploadTask removeObserver:self forKeyPath:@"state"];
    if (self.state == NSURLSessionTaskStateCompleted)
    {
      [self.uploadSession finishTasksAndInvalidate];
    }
    if (self.state == NSURLSessionTaskStateCanceling)
    {
        [self.uploadSession invalidateAndCancel];
    }
}

通過添加上面三個(gè)方法 再次 profile文件上傳模塊發(fā)現(xiàn)內(nèi)存泄露消失了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容