【问题标题】:NSURLSession memory leakNSURLSession 内存泄漏
【发布时间】:2015-07-18 08:58:24
【问题描述】:

即使在使 NSURLSession 无效后,使用 Instruments 运行配置文件,一些名为 TubeManager、HTTPConnectionCache 和 HTTPConnectionCacheDictionary 的类(可能是私有的)仍然存在于内存中。

代码 sn-p 重现:

NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession* session = [NSURLSession sessionWithConfiguration:config];
NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.google.com"]];
NSURLSessionDataTask* sessionDataTask = [session dataTaskWithRequest:request
                                               completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
    [session finishTasksAndInvalidate];
}];
[sessionDataTask resume];

【问题讨论】:

  • 我不确定你是如何测量内存增长的,但是如果我查看总分配,下载 100 次,然后再次检查总分配,然后重复五次,我看到了峰值分配趋于平稳。如果我做一个内存警告,甚至其中一些会被恢复。现在,如果我查看某个范围内的分配变化或使用“世代”,它看起来正在增长,但是 net-net,它趋于平稳。此外,请确保您已关闭僵尸。
  • 您是否尝试过运行 Instruments 并查找我提到的类?
  • 是的,但它们不会继续增长(即我的应用程序不断回落到稳定状态)。
  • 请看一下这个截图:evernote.com/shard/s70/sh/14637ee5-11ea-4744-889d-56ca50b647da/…你没有得到类似的结果吗?您运行的是哪个版本的 iOS?
  • iOS 8.3.1。我注意到这三个对象中的每一个都只有两个实例。巧合的是,我只实例化了两个会话。您不会为每个请求实例化新会话,是吗?

标签: ios macos memory-management memory-leaks nsurlsession


【解决方案1】:

finishTasksAndInvalidate 在错误的地方调用... completionHandler 用于处理响应它与会话无关

这是正确的代码:

NSURLSessionConfiguration* config = [NSURLSessionConfigurationdefaultSessionConfiguration];
NSURLSession* session = [NSURLSession sessionWithConfiguration:config];
NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.google.com"]];
NSURLSessionDataTask* sessionDataTask = [session dataTaskWithRequest:request
                                           completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
   // handle response...
}];
[sessionDataTask resume];
[session finishTasksAndInvalidate];

【讨论】:

    【解决方案2】:

    请注意,在 iOS 9 上,安全框架会分配 appx。 4k 的 SSL 缓存数据,并在您第一次为新的 NSURLSession 对象恢复任务时将其计入您的应用程序。 Apple Technical Q&A QA1727 告诉我们这个 SSL 缓存无论如何都会持续 10 分钟,因为它是私有的并且完全由系统管理(因为安全!)。

    在您的代码示例中,您每次发出请求时都会创建一个新的 NSURLSession 对象。但是您只是使用 defaultSessionConfiguration 而没有指定可能存在强引用的委托,那么您应该做的只是使用单例 [NSURLSession sharedSession] 并使用 resetWithCompletionHandler 来清除非安全分配。或者,如果您想自定义配置,请制作自定义单例。

    • (NSURLSession *)sharedSession 讨论

    对于基本请求,URL 会话类提供了一个共享单例 为您提供合理的默认行为的会话对象。通过使用 共享会话,您可以将 URL 的内容提取到内存中 只需几行代码。

    与其他会话类型不同,您无需创建共享会话; 您只需通过调用 [NSURLSession sharedSession] 来请求它。作为一个 结果,您不提供委托或配置对象。 因此,对于共享会话:

       - You cannot obtain data incrementally as it arrives from the server.
       - You cannot significantly customize the default connection behavior.
       - Your ability to perform authentication is limited.
       - You cannot perform background downloads or uploads while your app is    
         not running.
    

    共享会话使用共享的 NSURLCache、NSHTTPCookieStorage、 和 NSURLCredentialStorage 对象,使用共享的自定义网络 协议列表(使用 registerClass: 和 unregisterClass: 配置), 并且基于默认配置。

    在使用共享会话时,您通常应该避免 自定义缓存、cookie 存储或凭证存储(除非 你已经用 NSURLConnection 这样做了),因为有一个非常 很有可能你最终会超越 默认会话,此时您必须重写所有这些 以适用于您的自定义 URL 会话的方式进行自定义。

    换句话说,如果您使用缓存、cookies、 身份验证或自定义网络协议,您可能应该是 使用 a 默认会话而不是 默认会话。

    (来自 NSURLSession 类参考...斜体是我的;P)

    如果您没有使用 Apple 提供的 sharedSession 单例,那么您至少应该从 Apple 那里获得启发,并使用会话属性滚动您自己的单例。会话的意义在于,它的目的不仅仅是一个请求,它的寿命更长。尽管他们的文档不清楚,Apple 提供了一个单例并将其称为“会话”这一事实表明会话对象的寿命比单个请求更长。

    是的,你应该在某个时候invalidateAndCancel,但不是在每个请求之后,即使每个请求都完全发送到不同的服务器(几乎从来没有这种情况)。如果您要中断对特定会话的引用,您只需要无效和取消;否则,您可以在会话上调用flushWithCompletionHandlerresetWithCompletionHandler刷新 会话对VM 的堆分配,或reset 以清除堆和VM 存储。 (另见我的回答here。)

    【讨论】:

      【解决方案3】:

      那么,问题是什么?是否要关闭缓存?网络响应通常会缓存在内存和持久存储中的 NSURLCache 中。

      如果此缓存使用有问题,请相应更改会话配置的requestCachePolicy。或者更改NSMutableURLRequestcachePolicy 本身。您还可以配置会话配置使用的URLCache 的最大大小,以限制 RAM 使用量和持久内存使用量。

      即使您关闭了缓存,作为一般规则,我们也不应该对 API 调用增加内存消耗感到惊讶,这不是您自己的过错。应用程序在第一次执行某些任务时遇到一些适度的内存消耗并不罕见。但是,当应用程序运行时,不应在后续迭代中看到相同的增长。在跟踪内存使用情况时,通常建议多次重复该任务并查看应用程序是否返回到某个稳定状态(这是可取的),或者它是否继续增长(这需要进一步调查以确保您的代码不是问题的根源)。但我们很少担心初始内存消耗,除非它非常严重。

      查看您的代码 sn-p,没有什么明显错误。我倾向于怀疑 iOS 的日常内存消耗。假设问题比一般缓存行为更广泛,如果每次应用执行此代码时内存消耗非常大和/或继续增长,那么请提供更多详细信息,我们可以帮助您进一步诊断。


      这是我的内存配置文件在四批每批 100 个请求之后的样子;加上我发出内存警告后的最后一个标志:

      (注意,这是一张合成图像,所以我可以向您展示第一批之前、第三批之前、最后一批之后以及我手动发布内存警告之后的内存。我将这些组合在一起以便更容易查看总分配是在这四个时间点。)

      【讨论】:

      • 即使更改 requestCachePolicyURLCache 像这样 config.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData; config.URLCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:nil]; 每个请求的内存消耗仍在增长......所以在执行 800 个请求后,内存消耗从 4mb 增长到大约 6mb,所以 2mb 不应该在那里
      • 见我上面的回答:安全框架的堆 SSL 缓存是私有的,然后每个新的会话对象在 10 分钟内增加 3.5k。这与 10 分钟后 2.7MB 的增长是一致的,但是除非有其他问题,否则你不会看到它从那里上升。所以运行仪器 25 分钟,从 15 分钟到 25 分钟,看看有多少增长。请注意,如果在此期间请求频率增加或减少,这将改变 SSL 缓存内存占用的“高水位”。
      猜你喜欢
      • 2020-11-09
      • 2017-01-17
      • 2015-06-01
      • 2018-01-14
      • 1970-01-01
      • 2014-02-28
      • 1970-01-01
      • 2011-10-08
      • 2013-01-20
      相关资源
      最近更新 更多