I found a leak in my code where archiving and unarchiving an NSURLResponse was causing a leak, and I can't figure out why.
- (void)doStuffWithResponse:(NSURLResponse *)response {
NSMutableData *saveData = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:saveData];
[archiver encodeObject:response forKey:#"response"];
// Encode other objects
[archiver finishDecoding];
[archiver release];
// Write data to disk
// release, clean up objects
}
- (void)retrieveResponseFromPath:(NSString *)path {
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:[NSData dataWithContentsOfFile:path]];
NSURLResponse *response = [unarchiver decodeObjectForKey:#"response"];
// The above line leaks!!
// decode other objects
// clean up memory and do other operations
}
Instruments reports a leak when I unarchive the NSURLResponse. If I comment that out and do not use it, there is no leak. What was interesting was instead I saved the pieces of the NSURLResponse there is no leak:
// Encode:
[archiver encodeObject:[response URL] forKey:#"URL"];
[archiver encodeObject:[response MIMEType] forKey:#"MIMEType"];
[archiver encodeObject:[NSNumber numberWithLongLong:[response expectedContentLength]] forKey:#"expectedContentLength"];
[archiver encodeObject:[response textEncodingName] forKey:#"textEncodingName"];
// Decode:
NSURL *url = [unarchiver decodeObjectForKey:#"URL"];
NSString *mimeType = [unarchiver decodeObjectForIKey:#"MIMEType"];
NSNumber *expectedContentLength = [unarchiver decodeObjectForKey:#"expectedContentLength"];
NSString *textEncodingName = [unarchiver decodeObjectForKey:#"textEncodingName"];
NSURLResponse* response = [[NSHTTPURLResponse alloc] initWithURL:url MIMEType:mimeType expectedContentLength:[expectedContentLength longLongValue] textEncodingName:textEncodingName];
Anyone know why this is? Is there a bug with archiving NSURLResponse or am I doing something wrong?
Memory management in Objective-C is as simple as knowing that any time you call something that has "alloc", "new", or "copy" in the method name (or if you retain it), then you must release it at some point. See this for more info: http://developer.apple.com/mac/library/documentation/cocoa/Conceptual/MemoryMgmt/Articles/mmRules.html
In your case, it appears that you call alloc to create an NSMutableData, but never release it (So [saveData release] at the end of doStuffWithResponse: may resolve at least one leak). From this code, this also appears to be the case with your alloc'ed NSKeyedUnarchiver and your alloc'ed NSURLResponse.
If you're not holding onto the value, like in an ivar, you can also just call autorelease right after alloc'ing, or use the class's autoreleased creators if available (e.g. [NSString stringWithFormat:] instead of [[NSString alloc] initWithFormat:]).
Selecting Build > Build and Analyze may also reveal such issues.
Related
I have the following code and it always gives me a crash when performing the request, any idea?
NSString *responseBody = [[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding];
NSLog(#"Response body is %#", responseBody);
NSDictionary *accessTokenRequestParams = [[NSMutableDictionary alloc] init];
[accessTokenRequestParams setValue:CONSUMER_KEY forKey:#"x_reverse_auth_target"];
[accessTokenRequestParams setValue:responseBody forKey:#"x_reverse_auth_parameters"];
NSURL *url2 = [NSURL URLWithString:#"https://api.twitter.com/oauth/access_token"];
TWRequest * accessTokenRequest = [[TWRequest alloc] initWithURL:url2 parameters:accessTokenRequestParams requestMethod:TWRequestMethodPOST];
if (selectionIndex != -1)
[accessTokenRequest setAccount:[self.twitterACAccounts objectAtIndex:selectionIndex]];
// execute the request
[accessTokenRequest performRequestWithHandler:
^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
NSString *responseStr =
[[NSString alloc] initWithData:responseData
encoding:NSUTF8StringEncoding];
NSLog(#"The user's info for your server:\n%#", responseStr);
}];
UPDATE:
Turning on NSZombieEnabled gives me
*** -[ACAccountStore typeForAccount:]: message sent to deallocated instance 0x7199c70
this is no where to be found
Your error looks completely different than your question. You have error in ACAccountStore class. Check your retain count when you accessing, manipulating, and store accounts (ACAccountStore). I think you are deallocated memory first and you are using some where.
Somewhere you call ACAccountStore typeForAccount. But the ACAccountStore is gone. Looking at the AcAccount docs, there are no special initializers, so likely in your code you have something like:
static ACAccountStore* accountStore = [[[ACAccountStore alloc] init] autorelease];
then in the completion for the request, the object has been cleaned away by the OS, but your accountStore still points to the old, now dangling pointer. The pointer may be 'static' or 'global' or a member of some other static or global object.
Look for ACAccountStore in your code.
Previously, I have an array in which I use NSKeyedArchiver to archieve.
[NSKeyedArchiver archiveRootObject:array toFile:docPath];
Called on applicationWillTerminate and applicationDidEnterBackground.
Upon starting up, in didFinishLaunchingWithOPtions, the array is unarchieved:
NSMutableArray *array = [NSKeyedUnarchiver unarchiveObjectWithFile:docPath];
Everything was good. Since then, I added a Singleton class for Settings variables. I would like to archive the Singleton as well. How would I do that?
The part that confuses me is if I call the same message:
[NSKeyedArchiver archiveRootObject:singleton toFile:docPath];
How can the app know which object I want to unarchive when I call:
[NSKeyedUnarchiver unarchiveObjectWithFile:docPath];
Is it the array or the singleton?
The archiveRootObject accepts an (id) which to me means whatever I want. I can create a data class which includes the array and the singleton. Is that how I am suppose to do this? Is there a better way? Any suggestions are welcomed.
Thanks!
If you want to encode more than one object at the root level, you'll need to create the archiver using +alloc and -initForWritingWithMutableData: yourself, send it -encodeObject:forKey: messages for each of the objects you want to add to the archive, and finally -finishEncoding. You can then write the data to a file yourself.
It'll look something like this (warning: untested code ahead):
NSMutableData *data = [NSMutableData data];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:someObject forKey:#"people"];
[archiver encodeObject:anotherObject forKey:#"places"];
[archiver encodeObject:thirdObject forKey:#"things"];
[archiver finishEncoding];
[data writeToURL:someUrl atomically:YES];
[archiver release];
To retrieve the objects, you'll do essentially the opposite: read a file into an NSData object; create an NSKeyedUnarchiver with +alloc and -initForReadingWithData:; retrieve the objects you care about using -decodeObjectForKey:; and finally call -finishDecoding. You can almost read the sample above from bottom to top with a few name changes.
To be archivable, your singleton class must conform to the NSCoding protocol. You will have to implement its initWithCoder: and encodeWithCoder: methods.
Again if you do archiveRootObject:toFile: on two objects with the same file path then you will overwrite one of them. To encode two objects, you will have to do something like this,
NSData * data = [NSMutableData data];
NSKeyedArchiver * archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:array forKey:#"Array"];
[archiver encodeObject:singleton forKey:#"Singleton"];
[archiver finishEncoding];
BOOL result = [data writeToFile:filePath atomically:YES];
[archiver release];
You can read more on archiving in this guide.
I have an app where I import a bunch of data and I want to to happen on a background thread. This was working well for a small amount of data but now it seems I am running into this error midway through the parsing and importing of large amounts of my data into Core Data:
Program received signal: “EXC_BAD_ACCESS”.
Here is the call to the background thread:
[self performSelectorInBackground:#selector(importAllData) withObject:nil];
Here is an example of what I am doing in my code:
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
// load manufactuers from DB
NSFetchRequest *crequest = [[NSFetchRequest alloc] init];
NSEntityDescription *manufacturer = [NSEntityDescription entityForName:#"Manufacturer" inManagedObjectContext:managedObjectContext];
[crequest setEntity:manufacturer];
NSError *cerror=nil;;
NSArray *manufacturers = [[managedObjectContext executeFetchRequest:crequest error:&cerror]mutableCopy];
[crequest release];
for (int m=0; m < [manufacturers count]; m++) {
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:kClientListURL, [[manufacturers objectAtIndex:m]ManufacturerID]]];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setNumberOfTimesToRetryOnTimeout:2];
[request startSynchronous];
NSError *error = [request error];
if (!error) {
NSString *responseString = [request responseString];
NSArray *items = [responseString JSONValue];
NSNumberFormatter *f = [[NSNumberFormatter alloc] init];
NSNumberFormatter *dec = [[NSNumberFormatter alloc]init];
[dec setNumberStyle:NSNumberFormatterDecimalStyle];
for (int i = 0; i < [items count]; i++)
{
Client *entity = (Client*) [NSEntityDescription insertNewObjectForEntityForName:#"Client" inManagedObjectContext:managedObjectContext];
[entity setCompanyName:[[items objectAtIndex:i] objectForKey:#"CompanyName"]];
// set a bunch of other properties
[entity setManufacturer:[manufacturers objectAtIndex:m]];
statusMessage = [NSString stringWithFormat:#"importing client: %#", entity.CompanyName];
[self performSelectorOnMainThread:#selector(setStatus) withObject:nil waitUntilDone:YES];
}
[f release];
[dec release];
} else {
NSLog(#"%#",[NSString stringWithFormat:#"JSON parsing failed: %#", [error localizedDescription]]);
}
NSError *entityerror;
if (![managedObjectContext save:&entityerror]) {
// //Handle the error.
NSLog(#"\n\n\n Error saving clients: %# \n\n\n\n",entityerror);
}
}
//More data importing code
[pool release];
As I said this was working fine for a small amount of data but now in he simulator it throws bad access errors at random points.
Then I read this article which confused me:
http://www.cocoadev.com/index.pl?DebuggingAutorelease
Do I not need to release my objects if they are inside of the NSAutoReleasePool? Am I causing them to be released twice at some point?
If I remove the release statements I get potential leaks errors when building using Command-Shift-A?
The rules are:
If you are the owner, you need to either call [object release] or [object autorelease]. The later will delegate the release call to the NSAutoReleasePool, when the pool gets flushed or released.
You are an owner if you called [object retain] or if you got the object through a method that has either alloc or copy in its name.
If you got the object from a method that has neither alloc nor copy in its name and you haven't retained it you must not call release or autorelease on it (except in the very rare case that the documentations states that you are the owner).
So yes, if you got an autoreleased object and you call release on it you are over-releasing it. The effects are various but almost always lead to a crash, normally with EXC_BAD_ACCESS. You can debug this by setting the NSZombieEnabled environment variable. That way, whenever an object is deallocated it is replaced by a dummy that knows what object lived at that address before and can then tell you which object got over-released.
But reading through your code I didn't spot any immediately obvious problem, but I don't know Core Data (which you seem to be using) well enough. Another possibility is a threading/timing issue. Also, you said that's not the whole method ? Maybe the error is in the missing part.
I am assuming that importAllData is the method you are using. If so, you are missing the : at the end in your call.
[self performSelectorInBackground:#selector(importAllData) withObject:nil];
should be
[self performSelectorInBackground:#selector(importAllData:) withObject:nil];
Hope that helps!
I have a small iPhone app which has a button on the first view. When I select this button I load up my new view which has an image on it. I'm currently using the following code to load the image from an online source on a separate thread, whilst allowing the user to continue controlling the application:
- (void) loadImageTest
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSURL *url = [[NSURL alloc] init];
url = [NSURL URLWithString:logoPath];
NSData *data = [[NSData alloc] init];
data = [NSData dataWithContentsOfURL:url];
loadingImage = [UIImage imageWithData:data];
titleLogoImage.image = loadingImage;
//[pool drain];
[pool release];
}
This is called from this line of code in the new view's init:
[NSThread detachNewThreadSelector:#selector(loadImageTest) toTarget:self withObject:nil];
Now this works fine (ish), but if I close the new view and then load a new one up again in quick succession (or just after-wards in some cases) it will bomb out with the classic EXC_BAD_ACCESS. I'm sure that this is due to the code within the thread pool, but can anyone see why this is happening?
Thanks
Yours:
// This is ok, you might try using NSURLConnections asynchronous methods instead of making
// your own thread.
[NSThread detachNewThreadSelector:#selector(loadImageTest) toTarget:self withObject:nil];
- (void)loadImageTest
{
// This is fine
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// you're making and then abandoning this url object so it will leak
NSURL *url = [[NSURL alloc] init];
// this is fine
url = [NSURL URLWithString:logoPath];
// again making and abandoning an object
NSData *data = [[NSData alloc] init];
data = [NSData dataWithContentsOfURL:url];
// this works, but is not thread safe,
// can't operate on UIKit objects off the
// main thread
loadingImage = [UIImage imageWithData:data];
titleLogoImage.image = loadingImage;
// this is fine
//[pool drain];
[pool release];
}
Cleaned up to make things thread safe, etc. Should help:
// I'm assuming you have a iVar for logoPath but we don't want to
// make threaded calls to an iVar (it's not mutable, so you could do it, but it's just bad form)
// If i'm wrong about logoPath being an iVar don't sweat copying it.
- (void)whateverMethodYouWant
{
NSString *aLogoPath = [[logoPath copy] autorelease];
[NSThread detachNewThreadSelector:#selector(loadImageForPath:) toTarget:self withObject:aLogoPath];
}
- (void)loadImageForPath:(NSString *)aLogoPath
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:aLogoPath]];
// the performSelector will retain the data until the method can be performed
[self performSelectorOnMainThread:#selector(setImageForTitleLogo:) withObject:imgData waitUntilDone:NO];
[pool release];
}
- (void)setImageForTitleLogo:(NSData *)imgData
{
// This part is not strictly necessary
// but it's a nice way to wrap a method that needs to happen on the main thread.
if ([NSThread isMainThread])
{
// make the image (i'm assuming you meant loadingImage to be a local scope variable)
UIImage *loadingImage = [UIImage imageWithData:imgData];
// make sure the button still exists
// also make sure you're setting any references to this button to nil when you're releasing and making new views
// so that you're not addressing a button that's been released
// (assigning the image to the button will cause the button to retain it for you)
if (titleLogoImage != nil)
titleLogoImage.image = loadingImage;
}
else
{
[self performSelectorOnMainThread:#selector(setImageForTitleLogo:) withObject:imgData waitUntilDone:NO];
}
}
You're doing weird stuff.
NSURL *url = [[NSURL alloc] init];
means you create an NSURL object, which you own.
url = [NSURL URLWithString:logoPath];
This means you create another NSURL object, which is autoreleased. The NSURL you just created, just leaks. The same goes for the NSData here.
The loadingImage must be retained by titleLogoImage, or it will be deallocated on the drain of your NSAutoReleasePool. What is titleLogoImage and does it retain image?
edit ps: what also disturbes me, is that loadingImage is not confined to the scope of the function. To cut things short:
NSURL *url = [NSURL URLWithString:logoPath];
NSData *data = [NSData dataWithContentsOfURL:url];
titleLogoImage.image = [UIImage imageWithData:data];
may save some headaches, at least.
There is nothing in the posted code that would trigger a crash. Depending on how titleLogoImage is defined, it might be affects by the autorelease.
However, beyond the problems outlined by mvds, you have no pressing needs for a localized autorelease pool. It will do nothing here but cause you trouble.
Autorelease pools are dangerous and not for beginners. They will kill any autoreleased objects in them. You generally only create your own pool when you are rapidly creating a large number of memory intensive objects. That does not appear to be the case here.
Despite the attention given them, custom pools are seldom used. After over a decade Apple API work, I can probably count on my fingers the number of times I have used my own custom pool.
I have been living on Instruments for last few hours staring at a puzzling memory leak. I have isolated it to this single line of code in an NSOperation subclass I wrote:
NSData *myData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:myURLString]];
Periodically this will leak 3500 bytes. Is anyone else seeing this? If so, is there a work around?
Thanks in advance.
UPDATE:
Here is the relevant section of code within the main() body of my NSOperation subclass:
- (void)main {
// ...
NSData *sequenceData =
[[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:concatenatedURLString]];
NSString *sequenceString =
[[NSString alloc] initWithBytes:[sequenceData bytes] length:[sequenceData length] encoding:NSUTF8StringEncoding];
NSDictionary *result = [NSDictionary dictionaryWithObjectsAndKeys:
self.chromosome, #"chromosome",
[NSNumber numberWithInt:self.basepairStart], #"basepairStart",
[NSNumber numberWithInt:self.basepairEnd], #"basepairEnd",
sequenceData, #"sequenceData",
sequenceString, #"sequenceString",
nil];
[sequenceData release];
[sequenceString release];
[self.target performSelectorOnMainThread:self.action withObject:result waitUntilDone:NO];
}
As you can see sequenceData and sequenceString are properly released. Also, I have confirmed that all ivars of this subclass (chromosome. etc.) are properly memory managed.
-Doug
You have to release or autorelease myData, otherwise it will leak according to the Cocoa Memory Management Rules