NSURLSession 对 delegate 强引用造成的内存泄露

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

场景

在使用 profile查找项目中的内存泄露问题时发现在上传文件的模块中总是报内存泄露。自己封装的上传器工具类也没走 dealloc。Profile 只提示了我开始上传的方法里面存在泄露。

内存泄露的情况大概有

  • block 循环引用
  • delegate 强引用
  • 自定义对象之间互相持有
  • 系统对象和自定义对象之间互相持有
  • CoreFoundation 框架对象没有手动 Realese 掉

分析问题

经初步分析可能会有以下两个情况会导致 上传工具类不被释放

  • 上传完成之后上传管理器中的文件 URL <-->上传工具类之间的映射 map 没有将上传工具对象从 map 里面移除掉。
  • block 造成循环引用

按照上述的思路修改之后再次 profile 文件上传模块发现熟悉的内存泄露还是出现了。看到这里笑容渐渐消失了。确定了按上述思路修改没有遗漏的地方后我意识到可能是上传工具类内部处问题。

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

检查到上面代码的时候发现一个可疑点[NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:self.runQueue];
在给NSURLSession 设置 delegate 的时候,NSURLSession 会不会是对 delegate 进行了强引用?带着疑问去查了下文档

  • 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.

果然,没有猜错, NSURLSession对象强引用了我的上传工具类并且 NSURLSession对象又是上传工具类的一个属性。这样就形成了循环引用,这种情况属于

  • 系统对象强制持有 delegate 类

解决方案

破除循环引用的方法文档中也给出来了在下载完成时调用- (void)finishTasksAndInvalidate;, 取消当前会话中task任务并且是当前会话失效 调用- (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 *)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];
    }
}

通过添加上面三个方法 再次 profile文件上传模块发现内存泄露消失了。

你可能感兴趣的:(NSURLSession 对 delegate 强引用造成的内存泄露)