NSURLSession memory leak

2019-02-19 17:48发布

问题:

Even after invalidate a NSURLSession, running a profile using Instruments, some classes (probably privates) called TubeManager, HTTPConnectionCache and HTTPConnectionCacheDictionary still alive in memory.

Code snippet to reproduce:

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];

回答1:

So, what is the question? Do you want to turn off caching? Network responses are routinely cached in NSURLCache in both memory and persistent storage.

If this cache usage is problematic, change the requestCachePolicy for the session configuration accordingly. Or change the cachePolicy of a NSMutableURLRequest, itself. You can also configure the maximum size of the URLCache that the session configuration uses, to constrain both the amount of RAM usage as well as the amount of persistent memory usage.

Even if you turn off caching, as a general rule one should not be surprised that API calls increase memory consumption, through no fault of your own. It's not uncommon for an app to experience some modest memory consumption the first time it performs some task. But one should not see the same growth in subsequent iterations while the app runs. When tracking memory usage, it's generally advisable to repeat the task multiple times and see if the app returns back to some steady state (which is desirable), or whether it continues to grow (which requires further investigation to make sure your code is not the source of the problem). But we rarely worry about the initial memory consumption unless it is dramatic.

Looking at your code snippet, there's nothing obviously wrong. I'd be inclined to suspect routine memory consumption by iOS. Assuming the issue is broader than the general cache behavior, if the memory consumption is dramatic and/or continues to grow each time the app performs this code, then provide more details and we can help you diagnose this further.


This is what my memory profile looks like after four batches of 100 requests each; plus a final flag after I issued a memory warning:

(Note, that's a composite image so I could show you memory before the first batch, before the third batch, after the last batch and after I manually posted the memory warning. I combined these together to make it easier to see what the total allocations were at those four points in time.)



回答2:

Note that on iOS 9, Security framework allocates appx. 4k of SSL cache data and charges it to your app the first time you resume a task for a new NSURLSession object. Apple Technical Q&A QA1727 tells us this SSL cache persists for 10 minutes, no matter what, since it's private and entirely managed by the system (because security!).

In your code example, you are creating a new NSURLSession object each time you make a request. But you are just using defaultSessionConfiguration and not specifying a delegate to which a strong reference might exist, then what you should instead do is just use the singleton [NSURLSession sharedSession] and use resetWithCompletionHandler to clear up non-security allocations. Or make a custom singleton if you want to customize the configuration.

  • (NSURLSession *)sharedSession Discussion

For basic requests, the URL session class provides a shared singleton session object that gives you a reasonable default behavior. By using the shared session, you can fetch the contents of a URL to memory with just a few lines of code.

Unlike the other session types, you do not create the shared session; you merely request it by calling [NSURLSession sharedSession]. As a result, you don’t provide a delegate or a configuration object. Therefore, with the shared session:

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

The shared session uses the shared NSURLCache, NSHTTPCookieStorage, and NSURLCredentialStorage objects, uses a shared custom networking protocol list (configured with registerClass: and unregisterClass:), and is based on a default configuration.

When working with a shared session, you should generally avoid customizing the cache, cookie storage, or credential storage (unless you are already doing so with NSURLConnection), because there’s a very good chance that you’ll eventually outgrow the capabilities of the default session, at which point you’ll have to rewrite all of those customizations in a manner that works with your custom URL sessions.

In other words, if you’re doing anything with caches, cookies, authentication, or custom networking protocols, you should probably be using a default session instead of the default session.

(From NSURLSession Class Reference... Italics mine ;P)

If you are not using the sharedSession Apple-provided singleton, then you should at least take a cue from Apple and roll your own singleton with a session property. The point of a session is that it is intended to live longer than just one request. Despite their unclear documentation, the fact that Apple provides a singleton, and calls it a "session", indicates session objects were meant to live longer than just a single request.

Yeah, you are supposed to invalidateAndCancel at some point, but not after every single request, and not even if each request is going to a different server entirely (which is almost never the case). You only need to invalidate and cancel if you are going to break your reference to a particular session; otherwise, you can just call flushWithCompletionHandler or resetWithCompletionHandler on the session to flush your session's heap allocations to VM, or reset to clear both heap and VM storage. (Also see my answer here.)



回答3:

finishTasksAndInvalidate called in wrong place... completionHandler is for handling response it has nothing to do with session

Here is correct code:

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];