NSURLConnection leak?
Asked Answered
C

4

9

i have set up a nsurl which grabs the data from http. when i run instrument, it says i have a leak NSFNetwork object.

and how do i release theConnection in (void)ButtonClicked? or it will be release later on?

- (void)ButtonClicked {
    NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:KmlUrl]
                                                cachePolicy:NSURLRequestUseProtocolCachePolicy
                                            timeoutInterval:20.0f];

    NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
    if (theConnection) {
        // receivedData is declared as a method instance elsewhere
        NSMutableData *receivedData = [[NSMutableData data] retain];
        [self setKMLdata:receivedData];
    } else {
        // inform the user that the download could not be made
    }
}


- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    // append the new data to the receivedData
    // receivedData is declared as a method instance elsewhere
    [KMLdata appendData:data];
    NSLog(@"didReceiveData");
}


- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    // release the connection, and the data object
    [connection release];
    [KMLdata release];
}


- (void)connection:(NSURLConnection *)connection  didFailWithError:(NSError *)error
{
    // release the connection, and the data object
    [connection release];
    // receivedData is declared as a method instance elsewhere
    [KMLdata release];

}
Convert answered 28/8, 2009 at 8:15 Comment(0)
I
17

I finally found the answer for this.

The error in the above code (which by the way is the near-exact sample from the SDK docs) is not in the memory management code. Autorelease is one option, manual release is another. Regardless of how you handle your NSURLConnection object, you get leaks using NSURLConnection.

First up, here is the solution. Just copy these 3 lines of code directly into connectionDidFinishLoading, didFailWithError and anywhere else you release the NSURLConnection object.

NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:nil];
[NSURLCache setSharedURLCache:sharedCache];
[sharedCache release];

Credit to mpramodjain on http://forums.macrumors.com/showthread.php?t=573253 for the code.

The problem seems to be this – the SDK caches the requests and replies on the iPhone. Even it seems if your NSMutableURLRequest cachePolicy is set to not load the reply from the cache.

The silly thing is that it seems to cache a lot of data by default. I'm transmitting a lot of data (split into multiple connections) and started to get memory warnings, and finally my App died.

The docs we need are in NSURLCache (not NSURLConnection), they state:

NSURLCache implements the caching of responses to URL load requests by mapping NSURLRequest objects to NSCachedURLResponse objects. It is a composite of an in-memory and an on-disk cache.

Methods are provided to manipulate the sizes of each of these caches as well as to control the path on disk to use for persistent storage of cache data.

Those three lines have the effect of nuking the cache totally. After adding them to my App (GPS Log), my #living object count remains steady.

Instinctive answered 9/12, 2009 at 15:22 Comment(5)
for the record, I switched to the autorelease'd [NSURLConnection connectionWithRequest:request delegate:self] but I don't think it should matter.Instinctive
If your app makes any other NSURLRequests that should use caching, this solution will disable them. A better solution in that case might be to implement the cached response delegate (which has the same effect): -(NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse { return nil; }Tineid
A little further on what’s actually happening: NSURLCache is caching the response to the request, but it’s using the request as the key. Thus, it will hold onto the request as long as the responses are being cached. A 0-byte cache defeats this, at the expense of making all requests made by the app uncached; implementing the delegate method is a more tailored approach. Setting the request’s cache policy to ignore the cache does not suffice.Tineid
If you don't want to completely disable caching and can't or don't want to use asynchronous connection with delegate, you can call [[NSURLCache sharedURLCache] removeCachedResponseForRequest:request]; just after sending the requestThunell
I am using +[NSURLConnection sendAsynchronousRequest:queue:completionHandler:] and +[NSURLConnection sendSynchronousRequest:returningResponse:error] together with your method of setting cache, however, the living bytes keep increasing and eventually my app get killed after > 30 Mb just on those method alone. Do you have any idea? for sendSynchronousRequest, I am doing it inside dispatch_async with a SERIAL_QUEUE. Thanks!Oenone
B
5

Hello have you test this delegate method ?

- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
    return nil;
}

You can manage the cache more precisely.

"reset" NSURLCache *sharedCache can cause problems on other part of your code ?

Bock answered 24/7, 2011 at 14:32 Comment(0)
D
2

This is a common question and is solved by the magic of [object autorelease]. In your code this would be as follows:

NSURLConnection *theConnection = [[[NSURLConnection alloc] initWithRequest:theRequest delegate:self] autorelease];

In this way, the object is automatically added to the "autorelease pool" and dealloc'd at the start of the next run loop after it is no longer referenced.

Hope that helps

Edit: Also, I don't see why you're needing to call -retain on your receivedData variable.

Datary answered 28/8, 2009 at 8:21 Comment(4)
While calling autorelease will work, it will fail in the cases where the delegate is released before the connection is released. The better way would be to assign the connection to an instance variable and release+nil it when not needed. If the connection ivar is still assigned when the object deallocs, the delegate needs to be set to nil before releasing the connection.Kimberlite
@Kimberlite I agree your suggestion is more comprehensive, however it was clear that his question showed a basic misunderstanding of memory management on iPhone and therefore I wanted to give him a conceptually easier solutionDatary
@rpetrich, can you provide a code example. I've tried to solve this problem in many ways and still have a leak.Pinxit
@rpetrich, yes i would like to see your example as well.Convert
R
1

I am using the static method/autoreleased approach and it appears to work fine:

[NSURLConnection connectionWithRequest:theRequest delegate:self];

This way you don't even have to worry about releasing in the delegate callbacks. It turns out that the retain count of the connection is actually 2 (not 1) after it is alloc'd in the examples above, which changes the way I thought about this memory "leak."

@rpetrich I actually don't think you need to worry about the delegate being released before the connection is released. The connection retains it's delegate and the connection itself is actually retained by some sort of open connections queue. I wrote a blog post on my experiments with NSURLConnection on my blog:

"Potential leak of object" with NSURLConnection

Riella answered 7/1, 2012 at 6:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.