I know the basic principles about memory management (retain count, autorelease pools etc) in Cocoa, but once you go past simple retain/release, it's getting a bit more confusing. I couldn't find decent answers for those, as most tutorials cover simple scenarios. I would like to ask about best practices in how to write the code and avoid leaks.
1st question - iterations and temporary assigments:
for (id object in objectArray) {
Model *currentItem = object;
/* do something with currentItem */
[currentItem release];
}
If I remove the release in last line the code will work fine, but there's leak. What's the rule here? The object is already there in objectArray. I'm assigning it directly, to get the type. Should I do this some other way? Is this assignment increasing retainCount of currentItem? (is it something like [[alloc] initWithObject] ?) How to know if this assignment (object) is autoreleased or not?
2nd question - instant retains :
Model *model = [unarchiver decodeObjectForKey:@"ARCHIVED_MODEL_OBJECT"];
// it has to be here, because (I was told) unarchiver will return autorelease object
[model retain];
label.text = model.data;
How someone knew that this particular method works so weird, that I need to instantly call retain on returned value, or I will bump into null on next assignment? I couldn't find anything like this in documentation. According to retain/release rules, I would expect that decodeObjectForKey returns autorelased object, but it will take some time until the control goes back to app and pool claims the model object to be released. Is there any rule for that? How should I search for those?
3rd question - autoreleasing and passing variables:
- (IBAction) loadXMLButtonClicked:(id) sender {
objectArray = [self loadData]; // 1 - objectArray is instance var
NSArray *objectArray = [self loadData]; // 2 - objectArray is local var
// loadXMLButtonClicked is called on button click, here the method finishes
// and control goes back to application, autorelease pool is cleaned?
// case 1 - objectArray stays retained in instance variable? (because setter was used)
// case 2 - objectArray is soon to be released, there were no retains?
// (ignore the fact that it's local var, just hypothetically)
}
- (NSArray *) loadData {
NSArray *objectArray = [[NSArray alloc] init];
// populate array here
return [objectArray autorelease];
}
4th question - (bear with me, last one) parser xml best practice : (i don't want to use other solutions, using standard parser is to practive objective-c memory management and flow)
Basically this code here works, works good and have no leaks, but I don't really know if this is proper approach. I have separate object that acts as parser, parses an XML to collect an array of objects of type Model. Now, after parsing done, externally I would like to obtain that array, though I don't know how (is copying the array and releasing whole parser good idea?). Please run through the code and see the comments.
I've debug this code many times, using gdb for printing retainCounts, zombies, etc. and while I can manage to get this running and without leaks, I don't know 100% why and would like to hear a good reasoning how should this be done with explanation. That would be much appreciated.
Controller.m
- (NSArray *) loadData {
(...)
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
ModelXMLParser *parserDelegate = [[ModelXMLParser alloc] init];
[parser setDelegate:parserDelegate];
[parser parse];
objectArray = [[parserDelegate objectArray] copy];
// is this ok? i don't need the parser object so I think i should get rid of it
// and copy the data. how this copy works, is it shallow (only new reference to array)
// or deep copy (objects allocated again as well)?
// how to do deep copy of NSArray?
[parserDelegate release];
[parser release];
}
ModelXMLParser.m (simplified)
@implementation ModelXMLParser
@synthesize objectArray; // array of objects
@synthesize currentObject; // temporary object
@synthesize currentChars; // temporary chars
@synthesize parseChars; // parse chars only when there's need, leave those /t/n etc
- parser didStartElement (...) {
if ([elementName isEqualToString:@"objects"]) {
objectArray = [[NSMutableArray alloc] init];
}
else if ([elementName isEqualToString:@"object"]) {
currentObject = [[Model alloc] init];
}
else if ([elementName isEqualToString:@"name"]) {
// do i have to init currentObject.name (NSString) here? i guess not?
[self setParseChars:YES]; // just set the flag to make parse control easier
}
else if ([elementName isEqualToString:@"number"]) {
// int isn't object anyway, no init
[self setParseChars:YES]; // just set the flag to make parse control easier
}
}
- parser foundCharacters (...) {
if (parseChars) {
currentChars = [[NSString alloc] initWithString:string];
// why is currentChars retainCount = 2 here?
// is it like currentChars = [NSString new] and then currentChars = string? (so retain once more)
// is it good way to control parser? (please ignore the NSMutableString and appending example, try this one)
// should I just do currentChars = string here?
[currentChars autorelease]; // this is currently my solution, because there's no leak, but i feel it's incorrect?
}
}
- parser didEndElement (...) {
if ([elementName isEqualToString:@"object"]) {
[objectArray addObject:[currentObject copy]]; // should I copy here or just addObject, it retains anyway?
[currentObject release]; // I've initialized currentObject before, now I don't need it, so i guess retainCount goes to 0 here?
}
else if ([elementName isEqualToString:@"name"]) {
currentObject.name = currentChars; // is this correct, or shoud I do [currentChars copy] as well?
[self setParseChars:NO];
[currentChars release]; // as before, initialized, now releasing, but is this really correct?
}
else if ([elementName isEqualToString:@"number"]) {
currentObject.number = [currentChars intValue]; // is this correct, or shoud I do [currentChars copy] as well?
[self setParseChars:NO];
[currentChars release]; // as before, initialized, now releasing, but is this really correct?
}
}
- (void) dealloc {
// i shouldn't release currentChars or currentObject, those (i suppose) should be freed after parsing done,
// as a result of earlier releases?
[objectArray release];
[super dealloc];
}
Model.m
@implementation Model
@synthesize name; // this is NSString
@synthesize number; // this is int
- (id) copyWithZone:(NSZone *) zone {
Model *copy = [[[self class] allocWithZone:zone] init];
copy.name = [self.name copy];
copy.number = self.number;
return copy;
}
- (void) dealloc {
[name release];
// i dont have to release int, right? it's not an object
[super dealloc];
}
I'm especially confused with question 4. Sorry for maybe too long question, but this really is about one topic and deeper understanding of it.