Re: Debugging memory leak in NSURLSession with ARC
Re: Debugging memory leak in NSURLSession with ARC
- Subject: Re: Debugging memory leak in NSURLSession with ARC
- From: Graham Cox <email@hidden>
- Date: Fri, 02 Jan 2015 14:45:06 +1100
> On 2 Jan 2015, at 1:46 pm, Roland King <email@hidden> wrote:
>
> Having a handler block which refers to self is not in and of itself a problem, very many blocks implicitly do. The block retains self, however in most cases something else retains the block and the self reference goes away when the block is released. The problem usually comes when the handler block which refers to self is also a property of the object, eg myObj.completion = ^{ .. block referring to myobj }. Xcode normally warns you if you even get close to doing that however.
Right. Well, my block refers to self, but it's not a property of self. Quickly trying it with a weak version of self shows no change of behaviour regarding memory usage, so I guess that was a red herring.
Just to be sure, I have:
<my object>...[owns]...NSURLSession...[owns]...NSURLSessionDataTask...[owns]...completion block...[refers to]...<my object>
I don't think this amounts to a retain cycle, because when the session has finished the task, it will release it, and its block, and those references to self (my object) that it has.
>
> If that's happening then allocations should show you are amassing whatever objects those are and never releasing them, does it? Or you can be old skool about it and NSLog() dealloc() to see if it's getting called.
What appears to be amassing are 132KB malloc'd blocks (by the hundreds). These are created by HTTPNetStreamInfo::_readStreamClientCallBack(__CFReadStream*, unsigned long), down in CFNetwork. The stack trace is:
0 libsystem_malloc.dylib malloc_zone_malloc
1 libsystem_malloc.dylib malloc
2 CFNetwork HTTPNetStreamInfo::_readStreamClientCallBack(__CFReadStream*, unsigned long)
3 CFNetwork CFNetworkReadStream::_readStreamClientCallBackCallBack(__CFReadStream*, unsigned long, void*)
4 CoreFoundation _signalEventSync
5 CoreFoundation _cfstream_shared_signalEventSync
6 CoreFoundation __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
7 CoreFoundation __CFRunLoopDoSources0
8 CoreFoundation __CFRunLoopRun
9 CoreFoundation CFRunLoopRunSpecific
10 CFNetwork +[NSURLConnection(Loader) _resourceLoadLoop:]
11 Foundation __NSThread__main__
12 libsystem_pthread.dylib _pthread_body
13 libsystem_pthread.dylib _pthread_start
14 libsystem_pthread.dylib thread_start
Which actually doesn't directly link it to anything in my code, though I think it's safe to say it's something set up by NSURLSession or NSURLSessionDataTask, which I do create in my code.
BUT, and this is a big but, I'm not sure if I'm interpreting Allocations correctly. I have it set to "created and persistent", and graphing these blocks shows a very close corrlation between them and the overall memory usage profile, so my interpretation is that these are predominantly responsible for the memory usage of my app. However, the shape of the graph does show that a lot of these blocks are freed, but not all, leading to the gradual increase in baseline allocation.
Here's the rough outline of the code I'm using to run the NSURLSessionDataTasks. This is a method of <my object>, and it is called once when there is data to begin downloading, and runs itself as long as there are items in the queue to be processed.
- (BOOL) dequeueNextChunk
{
// removes next URL from the chunks queue and starts it asynchronously downloading as a session. Returns YES if a chunk was dequeued, NO if not (queue empty).
XVMediaChunk* chunk = [queuedChunkURLs firstObject];
if( chunk )
{
// dequeue
[queuedChunkURLs removeObjectAtIndex:0];
// schedule download and start it
[[self.session dataTaskWithURL:chunk.url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
if( httpResponse.statusCode == 200 )
{
// got a valid response. If it's a valid chunk add the data to the chunk object and pass it along to the next step.
if([httpResponse.MIMEType isEqualToString:@"video/mp2t"])
{
chunk.data = data;
[self processVideoChunk:chunk];
// and schedule the next chunk in the queue
[self performSelectorOnMainThread:@selector(dequeueNextChunk) withObject:nil waitUntilDone:NO];
}
}
else
{
// some other status was returned.
// code omitted that handes the error situation, which is not relevant to the leaks problem, as that occurs for valid responses only
}
}] resume];
return YES;
}
else
return NO;
}
In this code, the object XVMediaChunk is just a simple container that associates a URL with a sequence number in the .m3u8 playlist. It also allows the downloaded data to be attached, as it is here, and further processed. In fact the remainder of the processing is very simple - the data is written to a NSFileHandle and the XVMediaChunk is discarded.
The overall function of <my object> is to download a .m3u8 playlist, extract the media content URLs from the playlist and queue the media chunks in order. Each chunk is then downloaded by the above method one at a time. If the download succeeds, the next item in the queue is downloaded. This process sustains itself until the queue becomes empty or there's an error. Other code resumes this dequeuing and downloading process by polling for the playlist at the rate determined by the .m3u8 target schedule time - that all works and is likely outside the scope of my problem.
--Graham
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden