It's a really strange bug.
I'm use a code for download a file with NSURLConnection
if the download finish , I have no leak.
But if I cancel the download, I have 1Mo memory not release.
I have make test with instrument, and the methode identified to create this leak is
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
is really strange
this is my code for create, manage and cancel download
Note: this download is a part of core data managed object, but I think the core data is not responsible of my leak.
- (void)loadURL:(NSURL *)url
{
if (currentLoadingDatas) { // just bool for prevent multiple download
return;
}
currentLoadingDatas = YES;
NSURLRequest *request = [[NSURLRequest alloc]
initWithURL: url
cachePolicy: NSURLRequestReloadIgnoringLocalAndRemoteCacheData
timeoutInterval: 60
];
connectionDatas = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[request release];
}
#pragma mark NSURLConnection Delegates
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
if (!self.transientData) {
self.transientData = [[NSMutableData alloc] init];
}
[self.transientData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[self.transientData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[self willChangeValueForKey:@"datas"];
[self setValue:self.transientData forKey:@"datas"];
[self didChangeValueForKey:@"datas"];
[self.transientData release];
NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:nil];
[NSURLCache setSharedURLCache:sharedCache];
[sharedCache release];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:nil];
[NSURLCache setSharedURLCache:sharedCache];
[sharedCache release];
}
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
return nil;
}
-(void)cancelDownload
{
[self.connectionDatas cancel];
}
// fired by coreData
-(void)willTurnIntoFault
{
self.transientData = nil;
[self cancelDownload];
[super willTurnIntoFault];
}
// fired by coreData
-(void开发者_如何学运维)didTurnIntoFault
{
[connectionDatas release];
NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:nil];
[NSURLCache setSharedURLCache:sharedCache];
[sharedCache release];
[self.transientData release];
[super didTurnIntoFault];
}
Can you help me for identify the problem ?
Thanks a lot.
How is self.transientData
property declared ?
Because you initialize with : self.transientData = [[NSMutableData alloc] init];
and if the property is set to retain the value, you will need to release it two times.
If so, to set the property use : self.transientData = [[[NSMutableData alloc] init] autorelease];
or simply [NSMutableData data];
.
The rest seems ok to me.
The leak is indicating that your variable is instantiated at that point so that's not where the leak actually is that is where it starts. You need to release the transientData in your cancel method like this
- (void)cancelDownload
{
[self.connectionDatas cancel];
self.transientData = nil;
}
There are a few issues with inconsistency in your coding style that may make it harder for you to mentally track what is going. If you stick to your own standards it should be easier to follow the flow.
This is potentially where another leak could occur
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
if (!self.transientData) {
self.transientData = [[NSMutableData alloc] init]; // leaky line
}
[self.transientData setLength:0];
}
The [[NSMutableData alloc] init]
call is creating an instance of NSMutableData
with a retain count of +1. Then if you are using properties (which are best) with retain
then the self.transientData =
will take another retain adding another +1 to the retain count. This is later released only once therefore you have a leak as the NSMutableData
will still be hanging around.
Further down in your code you use the pattern
- create instance and assign to local variable
- assign instance to ivar
- release local variable instance
This is the pattern I would use when I am not in an init
method. Therefore the method before should be changed to:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
if (!self.transientData) {
NSMutableData *tmpTransientData = [[NSMutableData alloc] init];
self.transientData = tmpTransientData;
[tmpTransientData release];
}
[self.transientData setLength:0];
}
This has the benefit of not using an autorelease so you are more explicit about when the object is no longer required which is preferred when possible on devices with small memory.
Another inconsistency which might benefit being tidied up a little is how you release your ivars. You have interchangeably done this in your code
[self.transientData release];
self.transientData = nil;
I would use the latter in my code (that is not in dealloc
) for instance variables as the synthesized
setter will call release
for you and set the pointer to nil
which is considerably safer.
精彩评论