NSKeyedUnarchiver causing memory leak - iphone

I have a memory leak problem, I'm saving an array into a file using:
[NSKeyedArchiver archiveRootObject:myArray toFile:MyFile];
the objects included into the array have the following methods:
- (id)initWithCoder:(NSCoder *)coder
{
[super init];
parameter1 = [[coder decodeObject] retain];
parameter2 = [[coder decodeObject] retain];
parameter3 = [[coder decodeObject] retain];
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeObject:parameter1];
[coder encodeObject:parameter2];
[coder encodeObject:parameter3];
}
To unarchive the objects I'm using:
myUnarchivedArray = [NSKeyedUnarchiver unarchiveObjectWithFile:myFile];
The application suddenly crashes because the available memory is not enough to continue.
I'm unable to deallocate myUnarchivedArray and the Intruments tool is telling me that the unarchiver is causing the memory leak.
I haven't search too much, I just found the cause of the memory leak, but I was hopping to find someone that has been passed the same problem and has a tip to solve it.:)
Thank you!!
Anna

You're retaining your objects in initWithCoder although the documentation states:
NSKeyedUnarchiver’s implementation, however, returns an autoreleased object, so its life is the same as the current autorelease pool instead of the keyed unarchiver.

Related

Memory Management Headache

I get leaks if I dont put it in dealloc. I get a crash EXC_BAD_ACCESS If I do. I cannot see anything wrong with this code. The bad access is pointed at [events release]. Have I made a mistake in the code below or is Instruments just having a laugh at my expense?
events is an NSArray
#interface EventsViewController : UITableViewController
{
#private
NSArray *events;
}
- (void)viewDidLoad
{
events = [[self getEvents] retain];
}
- (void)dealloc
{
[events release];
[super dealloc];
}
- (NSArray*)getEvents
{
NSMutableArray *response = [[[NSMutableArray alloc] init] autorelease];
//Some sql
while(sqlite3_step(statement) == SQLITE_ROW)
{
Event *event = [[[Event alloc] init] autorelease];
event.subject = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 0)];
[response addObject:event];
}
return response;
}
Update
A lot of you are saying the code is fine which is a plus. I dont manipulate events elsewhere - I have removed any code that does to try and single out the crash. Perhaps its in the parent view?
This is the click event that pushes the EventsViewController:
- (void)eventsClick:(id)sender
{
EventsViewController *eventsViewController = [[EventsViewController alloc] initWithNibName:#"EventsViewController" bundle:nil];
eventsViewController.anywhereConnection = anywhereConnection;
eventsViewController.contact = contact;
[[self navigationController] pushViewController:eventsViewController animated:YES];
[eventsViewController release];
}
The crash is actually happening when I return to the parent view. (I think it is considered a parent in this scenario). But perhaps the [eventsViewController release] just triggers dealloc in the EventViewController.
Have you considered just refactoring your code to use ARC? It works with iOS 4 and up and will make your life a lot easier. There are plenty of tutorials out there that will guide you how to do it, and will remove the need to manually figure out the nuances of memory management.
If your Events object has property 'subject' set as assign, then the results of stringWithUTF8String: will not be retained. (Same thing if Events is a C++ object.)
The stringWithUTF8String: method returns an auto-released object that will be released at the next turn of the event loop.
There is a huge difference when you reference a variable via "self", and when you don't.
When you use
events = [[self getEvents] retain];
the memory allocated in getEvents never gets stored in the class property and is basically a leak.
You need to use
self.events = [self getEvents]; // no need to use retain if property is correctly defined.
Then
[events release];
should work fine.
try putting
events = nil;
in dealloc.

iphone: How to solve NSArray memory Leak?

I am releasing NSArray and NSMutableArray but its show memory leak. while ZoneData code is like this
-(ZoneData*) initWithZoneName:(NSString *)zoneNameIn SdName:(NSString *)sdNameIn eCount:(NSString *)eCountIn iCount:(NSString *)iCountIn StandLat:(NSString *)standLatIn StandLong:(NSString *)standLongIn
{
self = [super init];
if (self)
{
zoneName = [zoneNameIn copy];
lsdName = [sdNameIn copy];
leCount = [eCountIn intValue];
liCount = [iCountIn intValue];
standLat = [standLatIn copy];
standLong = [standLongIn copy];
}
return self;
}
how to solve this?
The problem is your instance variables. In your -init, you are correctly assigning them to copies of the strings from the array. However, you need t also release them in -dealloc.
-(void) dealloc
{
[zoneName release];
[lsdName release];
[standLat release];
[standLong release];
[super dealloc];
}
Now, you may be asking why the leaks tool is telling you the leaks are where you create the NSArray with the strings in it instead of the init method. The reason is that -copy for immutable objects is optimised to do nothing except send retain to self. So those copies you have as instance variables are in reality the same objects as was created by -componentsSeparatedByString:
componentsSeparatedByString: returns an autoreleased NSArray. You are not supposed to release that yourself, but the closest NSAutoreleasePool will do that for you. In line 61 you are overreleasing the array.
If you are concerned about the memory usage while performing the loop you can clear autoreleased objects in each iteration of the loop:
for (...)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// your loop contents.
[pool drain];
}

NSData memory leak

Instruments is showing that i get a memory leak right there:
-(id) copyWithZone: (NSZone *) zone
{
Layer *copy = [[Layer allocWithZone:zone]init];
NSData *imageData = [[NSData alloc]initWithData:_image];
copy.image = imageData;
[imageData release];
return copy;
}
The image property is declared as it follows:
#property (nonatomic, retain) NSData *image;
Here is a screenshot of instruments, to prove that i am not lying.
Anyone see a problem in there?
The Leaks instruments shows you where an object originated, not where it "leaked". So somewhere in your code you'll have something like this:
MyClass *obj = [otherObj copy]; // or copyWithZone:
But you're not releasing or autoreleasing obj and thus create a leak.
In Objective-C, convention tells you a method should return an autoreleased object, except for methods that start with alloc, new, copy or mutableCopy. These method must return a retained object instead and the receiver is the owner and thus responsible for releasing them.
See Memory Management Policy in Apple's memory management guide.
Here is how we solved it, following the instructions given here.
-(id) copyWithZone: (NSZone *) zone
{
Layer *copy = [[Layer allocWithZone:zone]init];
copy->_image=nil;
[copy setImage:[self image]];
return copy;
}
- (id)copyWithZone:(NSZone *)zone{
Layer *copy = [[[self class] allocWithZone: zone] init];
[copy setImage:[self image]];
return copy;
}

Array doesn't persist outside viewDidLoad

I'm facing a strange problem with NSUsrDefaults. Whenever I'm fetching the data from NSUserDefaults, it's getting populated temporarily. I'm fetching it into viewDidLoad where it's fetched.
-(void)viewDidLoad{
companies = [NSMutableArray array];
oldCompanies = [[NSUserDefaults standardUserDefaults] arrayForKey:#"companyData"];
if( companies )
{
for( NSData *data in oldCompanies )
{
companyObj = (Company*) [NSKeyedUnarchiver unarchiveObjectWithData:data];
[companies addObject:companyObj];
}
}
}
But outside viewDidLoad, whenever I try to access the data, the array "oldCompanies" as well as "companies" are shown "nil".
EDIT:
I'm encoding my Company object in a class which subclasses NSCoding like shown below but not allocating or retaining the properties anywhere. Can this be the catch?
- (void)encodeWithCoder:(NSCoder *)encoder
{
//Encode properties, other class variables, etc
[encoder encodeObject:self.companyId forKey:#"id"];
[encoder encodeObject:self.companyTitle forKey:#"title"];
[encoder encodeObject:self.companyImage forKey:#"image"];
}
- (id)initWithCoder:(NSCoder *)decoder
{
self = [super init];
if( self != nil )
{
//decode properties, other class vars
self.companyId = [decoder decodeObjectForKey:#"id"];
self.companyTitle = [decoder decodeObjectForKey:#"title"];
self.companyImage = [decoder decodeObjectForKey:#"image"];
}
return self;
}
Can anybody please help?
Thanx in advance.
+array creates an autoreleased array - if you want to take ownership of it per the memory management rules then you need to retain it:
[companies retain];
Or create it so that it isn't autoreleased:
companies = [[NSMutableArray alloc] init];
Or, better, let declared properties do that for you:
// interface, property declaration:
#property(retain, readwrite) NSMutableArray *companies;
// implementation:
#synthesize companies;
// ... using it:
self.companies = [NSMutableArray array];
You are not retaining the array, when you dont do an alloc, or a retain when instatiating an object you get an autoreleased object, in your example companies is autoreleased and is why you cant access it anymore at a later point you should either alloc it
[[NSMutableArray alloc] init] or retain it [NSMutableArray array] retain]...either way refer to memory managment guide to learn about objective-c memory managment memory managment ref
Are you trying to access the data before the view is loaded?
Objective-C doesn't reset your pointers for you. If the array isn't "persisted", then the pointer will point to garbage.

iPhone Serialization problem

I need to save my own created class to file, I found on the internet, that good approach is to use NSKeyedArchiver and NSKeyedUnarchiver
My class definition looks like this:
#interface Game : NSObject <NSCoding> {
NSMutableString *strCompleteWord;
NSMutableString *strWordToGuess;
NSMutableArray *arGuessedLetters; //This array stores characters
NSMutableArray *arGuessedLettersPos; //This array stores CGRects
NSInteger iScore;
NSInteger iLives;
NSInteger iRocksFallen;
BOOL bGameCompleted;
BOOL bGameOver;
}
I've implemented methods initWithCoder: and encodeWithCoder: this way:
- (id)initWithCoder:(NSCoder *)coder
{
if([coder allowsKeyedCoding])
{
strCompleteWord = [[coder decodeObjectForKey:#"CompletedWord"] copy];
strWordToGuess = [[coder decodeObjectForKey:#"WordToGuess"] copy];
arGuessedLetters = [[coder decodeObjectForKey:#"GuessedLetters"] retain];
// arGuessedLettersPos = [[coder decodeObjectForKey:#"GuessedLettersPos"] retain];
iScore = [coder decodeIntegerForKey:#"Score"];
iLives = [coder decodeIntegerForKey:#"Lives"];
iRocksFallen = [coder decodeIntegerForKey:#"RocksFallen"];
bGameCompleted = [coder decodeBoolForKey:#"GameCompleted"];
bGameOver = [coder decodeBoolForKey:#"GameOver"];
}
else
{
strCompleteWord = [[coder decodeObject] retain];
strWordToGuess = [[coder decodeObject] retain];
arGuessedLetters = [[coder decodeObject] retain];
// arGuessedLettersPos = [[coder decodeObject] retain];
[coder decodeValueOfObjCType:#encode(NSInteger) at:&iScore];
[coder decodeValueOfObjCType:#encode(NSInteger) at:&iLives];
[coder decodeValueOfObjCType:#encode(NSInteger) at:&iRocksFallen];
[coder decodeValueOfObjCType:#encode(BOOL) at:&bGameCompleted];
[coder decodeValueOfObjCType:#encode(BOOL) at:&bGameOver];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder
{
if([coder allowsKeyedCoding])
{
[coder encodeObject:strCompleteWord forKey:#"CompleteWord"];
[coder encodeObject:strWordToGuess forKey:#"WordToGuess"];
[coder encodeObject:arGuessedLetters forKey:#"GuessedLetters"];
//[coder encodeObject:arGuessedLettersPos forKey:#"GuessedLettersPos"];
[coder encodeInteger:iScore forKey:#"Score"];
[coder encodeInteger:iLives forKey:#"Lives"];
[coder encodeInteger:iRocksFallen forKey:#"RocksFallen"];
[coder encodeBool:bGameCompleted forKey:#"GameCompleted"];
[coder encodeBool:bGameOver forKey:#"GameOver"];
}
else
{
[coder encodeObject:strCompleteWord];
[coder encodeObject:strWordToGuess];
[coder encodeObject:arGuessedLetters];
//[coder encodeObject:arGuessedLettersPos];
[coder encodeValueOfObjCType:#encode(NSInteger) at:&iScore];
[coder encodeValueOfObjCType:#encode(NSInteger) at:&iLives];
[coder encodeValueOfObjCType:#encode(NSInteger) at:&iRocksFallen];
[coder encodeValueOfObjCType:#encode(BOOL) at:&bGameCompleted];
[coder encodeValueOfObjCType:#encode(BOOL) at:&bGameOver];
}
}
And I use these methods to archive and unarchive data:
[NSKeyedArchiver archiveRootObject:currentGame toFile:strPath];
Game *currentGame = [NSKeyedUnarchiver unarchiveObjectWithFile:strPath];
I have two problems.
1) As you can see, lines with arGuessedLettersPos is commented, it's because every time I try to encode this array, error comes up(this archiver cannot encode structs), and this array is used for storing CGRect structs.
I've seen solution on the internet. The thing is, that every CGRect in the array is converted to an NSString (using NSStringFromCGRect()) and then saved. Is it a good approach?
2)This is bigger problem for me. Even if I comment this line and then run the code successfully, then save(archive) the data and then try to load (unarchive) them, no data is loaded. There aren't any error but currentGame object does not have data that should be loaded.
Could you please give me some advice? This is first time I'm using archivers and unarchivers.
Thanks a lot for every reply.
The problem with loading and saving solved another way...
Instead of implementing - (id)initWithCoder:(NSCoder )coder and - (void)encodeWithCoder:(NSCoder)coder I used this solution:
NSMutableData *data = [NSMutableData alloc];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:self.strCompleteWord forKey:#"CompleteWord"];
[archiver encodeObject:self.strWordToGuess forKey:#"WordToGuess"];
[archiver encodeObject:self.arGuessedLetters forKey:#"GuessedLetters"];
//[coder encodeObject:self.arGuessedLettersPos forKey:#"GuessedLettersPos"];
[archiver encodeInteger:self.iScore forKey:#"Score"];
[archiver encodeInteger:self.iLives forKey:#"Lives"];
[archiver encodeInteger:self.iRocksFallen forKey:#"RocksFallen"];
[archiver encodeBool:self.bGameCompleted forKey:#"GameCompleted"];
[archiver encodeBool:self.bGameOver forKey:#"GameOver"];
[archiver finishEncoding];
[data writeToFile:strPath atomically:YES];
[data release];
and
NSMutableData *data = [[NSMutableData alloc] initWithContentsOfFile:strPath];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
self.strCompleteWord = [[unarchiver decodeObjectForKey:#"CompletedWord"] copy];
self.strWordToGuess = [[unarchiver decodeObjectForKey:#"WordToGuess"] copy];
self.arGuessedLetters = [[unarchiver decodeObjectForKey:#"GuessedLetters"] retain];
//self.arGuessedLettersPos = [[unarchiver decodeObjectForKey:#"GuessedLettersPos"] retain];
self.iScore = [unarchiver decodeIntegerForKey:#"Score"];
self.iLives = [unarchiver decodeIntegerForKey:#"Lives"];
self.iRocksFallen = [unarchiver decodeIntegerForKey:#"RocksFallen"];
self.bGameCompleted = [unarchiver decodeBoolForKey:#"GameCompleted"];
self.bGameOver = [unarchiver decodeBoolForKey:#"GameOver"];
[unarchiver finishDecoding];
[data release];
And this works totally fine :)
I might be missing it, but I don't see any obvious bugs in this code.
Here are some ideas that might help:
Add some NSLog statements, and watch the debug output (open with command-shift-R in xcode) to see if your encode/decode methods are actually being called.
Check that the archive file is saved: Running in the simulator, you can save to any local path you want, such as /tmp/my_archive_file. Try to save to that file, and see if (a) the file exists with the right timestamp, and (b) you print out the file, you can see some recognizable strings (like "RocksFallen") in amongst the binary gooblygoo.
I also don't think it's necessary to check for allowsKeyed(En)coding since you know that's always going to be true when you're explicitly using NSKeyed(Un)archiver to do your dirty work for you. So you can throw away the other half of your code.
About coding those arrays of CGRects: I don't think you can directly add structs to an NSMutableArray, right? So they must be some kind of object, which means you can add NSCoding support to that object. If it's not your own object, you can subclass it and add that protocol, or try adding it as a category.
Thanks for reply Tyler.
I looked in the saved file and there are recognizable strings like RocksFallen and so on, also there are some recognizable values that might be saved in string variables.. So this seems to be good.
I tried to put some NSLogs in the code and not everything seems to be good. So...
When I launch simulator for the first time, there's no archive file, yes it is obvious, because nothing is saved. Then I change something and exit the application. When the application is being terminated, data might be archived, everything's fine. NSLog statements are written in the console, file is on the disk. But what is strange is that when I launch the application then, decode method(initwithcoder) is not being called.. Don't know why. Then I exit the simulator and run the simulator again. When I run it again, decode method is being called at the launchtime. But only at the first launchtime after running simulator, when I then work with simulator like exit the app and run it again, initWithCoder method is not being called and that's very strange for me..
So there are problems unarchiving it i think.
The best way to archive structs (that are not holding any pointers) with would be to wrap the values in an NSData block.
here, i'm dumping a struct array to the archiver:
for (int i = 0; i < items; i++) {
NSString *keyValue = [NSString stringWithFormat:#"YourStruct%i", i ];
NSData *dataset = [NSData dataWithBytes:(void*)&activeprofile[i] length:(sizeof(profileset))];
[encoder encodeObject:dataset forKey:keyValue]; // makes a nice, solid data block
}
and reading the data back in:
for (int i = 0; i < items; i++) {
NSString *keyValue = [NSString stringWithFormat:#"YourStruct%i", i ];
NSData *dataset = [decoder decodeObjectForKey:keyValue];
[dataset getBytes:&activeprofile[i] length:sizeof(profileset)];
}
There you have it: easy as pie and extremely flexible with datatypes!
A similar method for non-keyed archiving can be found here: http://borkware.com/quickies/one?topic=NSCoder Though I suggest sticking with keyed archiving, as NSArchive is not supported on the iPhone and has been classified as legacy code.