Objective-C memory management, XML parser and other non-trivial examples

I know the basic principles of memory management (save count, autoplay pools, etc.) in Cocoa, but as soon as you pass by a simple save / release, it gets a little more confusing. I could not find decent answers for them, since most of the manuals cover simple scenarios. I would like to ask about the best code writing methods and avoid leaks.


1st question - iterations and temporary assignments:

for (id object in objectArray) { Model *currentItem = object; /* do something with currentItem */ [currentItem release]; } 

If I remove the release on the last line, the code will work fine, but there will be a leak. What is the rule here? An object already exists in objectArray. I assign it directly to get the type. Should I do it differently? Does this assignment increase the value of keepCount currentItem? (is it something like [[alloc] initWithObject]?) How do I know if this assignment (object) is auto-implemented or not?


The second question is instant save:

 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 did someone know that this particular method works so strange that I need to instantly call hold on the returned value, or will I hit zero on the next assignment? I could not find anything like this in the documentation. According to save / release rules, I would expect decodeObjectForKey to return an auto-pushed object, but it will take some time until the control returns to the application and the pool claims that the model object will be released. Is there any rule for this? How to look for them?


The third question is auto-implementing 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 local var, just hypothetically) } - (NSArray *) loadData { NSArray *objectArray = [[NSArray alloc] init]; // populate array here return [objectArray autorelease]; } 

4th question - (bear with me, the last one) xml parser: (I don't want to use other solutions using a standard parser to practice objective-c control and memory flow)

Basically, this code works here, works well and has no leaks, but I really don't know if this approach is suitable. I have a separate object that acts as a parser, parses XML to collect an array of objects of type Model. Now, after doing the parsing, I would like to get this array from the outside, although I don't know how (copies the array and frees the entire parser with a good idea?). Read the code and review the comments.

I debugged this code many times using gdb to print saveCounts, zombies, etc., and although I manage to run it without leaks, I don’t know 100% why I would like to hear a good argument about how this should be done with an 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 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 no leak, but I feel it 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 don't have to release int, right? it not an object [super dealloc]; } 

I am particularly confused by question 4. Sorry, maybe the question is too long, but this is really one topic and a deeper understanding of this.

+7
memory objective-c iphone
source share
7 answers

1st question - iterations and timing

You should not release objects here, since you are not the owner. You should only release an object if you own it. See Memory Management Guide for Cocoa . You own an object if you call a method whose name begins with init , new or contains copy in its name.

Because the for loop does not use methods with any of these names, you are not the owner, so you SHOULD NOT issue objects. This will free the objects before they finish, which will almost certainly lead to memory corruption and crash.

The second question is instant save:

You do not need to instantly call hold, you just need to call him before the autostart pool is empty. This is likely to happen shortly after your method returns to the main event loop. Since you do not know exactly when this will happen, you must make sure that if you want to access the object after the function ( loadXMLButtonClicked: returns), you must retain this before you return.

Since decodeObjectForKey does not start with init or new or contains copy in its name, you do not become the owner. The retain call makes you the owner.

The third question is auto-implementation and transfer of variables:

First of all, it is bad practice to obscure a class member with a local variable of the same name. Secondly, if loadData not used as a universal utility function (which, I assume, is not because it does not accept any parameters), it should simply assign the result directly to the member variable objectArray . This is pointless and error prone to return the result , and then assign the calling function to the member variable.

Thirdly, you do not use the property objectArray - you simply assign directly to a member variable. If you want to use setter, you must explicitly say self.objectArray = ... (with self. front of it). This way, objectArray never saved, so it will be freed the next time the auto-advertisement pool is empty, which you don’t need. You must save it at some point or, conversely, just do not autoresist it at the end of loadData and assign it a member variable of the objectArray class.

If the property is declared with the retain attribute, then with the help of the installer it will automatically call retain when assigned with it (and there will also be release old value). If instead the property is declared with the copy attribute, then the value will be copied each time it is assigned to it, and you will become the owner of a new object.

4th question - good practice xml parser:

You make a shallow copy of an array of objects. If you want to make a deep copy, you can use the message initWithArray:copyItems:

Do I need to initialize currentObject.name (NSString) here? I do not think?

I do not understand this question, there currentObject.name not mentioned anywhere near this code.

why currentChars saveCount = 2 here?

Probably because during the internal initialization process this retain time was edited for extra time, but it was also almost certainly autoreleased extra time. If you follow all the rules in the Cocoa Memory Management Guide, you won’t have any problems. You should never rely on accounts because you do not know how many times an object has been auto-implemented. This is debugging help, not something that should be used for program control flow.

This is currently my decision, because there is no leak, but I feel this is not true?

If you don't need to use currentChars next time you return to the event loop, then that would be fine. If you need to use it, you should not release or auto-update it here, and then let it go when you are sure that it is all over.

should i copy here or just addObject, is it still saved?

Just addObject : when elements are added to NSArray , NSSet or NSDictionary they are automatically retain editable by the data structure. When you delete them, they release d.

You can answer most of the remaining questions simply by following the rules or by getting the same answers to some of the previous questions that I already answered.

+7
source share

@ 1st question

You get only a reference to the object in the Array object. It is still in the Array object, which also saves the object and frees it because you did not do anything that saved it.

See here for some rules.

+1
source share

@Second question

It looks like you are setting the text property of UILabel, which in this case uses a copy. The documentation says:

 @property(nonatomic, copy) NSString *text; 

This means that the shortcut will copy and save this copy without changing or saving the object used to assign the property.

0
source share

@Third question

The string " objectArray = [self loadData]; // 1 - objectArray is instance var " is not really a setter when directly accessing an instance variable. To use the setter, you need to access it, although self

 self.objectArray = [self loadData]; 

... and if your property is declared as (nonatomic, copy) , the old Array object will be released and the new one will be created with the copy, therefore it is saved.

0
source share

I think a good reading of Apple Cocoa's memory management rules is in order. Many of your questions are answered by experts in this document.

0
source share

Thanks to everyone for the answers. These answers helped, albeit in few places, I had to figure out the rest myself. I focused a lot more on this idea rather than a linear implementation, but it was hard to describe it here without inserting huge chunks of code. It's just that this delegate for parsing XML objects is a very specific example, since it does not return a value as such, the value must be accepted and assigned from the outside.

I note that Adam answers as the best, as the most detailed, although not answering all my problems.

For others - http://developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/MemoryMgmt.html is a great read. Also read my own answers to questions:

1 - Of course, I should not let go of this object. A memory leak was not caused by this.

2 - this method is nothing special, it just returns a simple auto-implemented object, as others tried to explain to me here. The root of my initial problems was that I didn’t have this before, but soon called [model release], as a result of which the autoplay pool sent the release to a non-existent object. I should not do [model release] because I do not own this object. In fact, this retention is not even required here, because I just need an object to get value from it, and then I can break it, so it can be safely transferred to the autorun pool without saving.

3 - I intended this method (loadData) to be independent, so I did not set any instance variables, but returned an array for others. This was a parallel example, it is not that I have two variables with the same name in the method.

If I declare an object in this method (situation No. 2), it just happens that it is automatically solved at the end of this method, because after its completion the control returns to the application and deletes the pool. ok with me in this example because later I don't need an array. In the real world, probably I should have an instance variable (situation # 1), and then go with self.objectArray = [self loadData], because it will run setter, and the object with auto-implementation will be saved for me.

4 - I was a little confused here. Basically I tried to code in objecive-c with manual memory management, but still had a garbage collector related. It is very important to remember that if you execute [[object alloc] init], and then later [release the object] - it should not automatically be that the object is destroyed! The release is not nullifying! This, of course, is a fundamental rule (preservation / liberation), but even knowing it is easy to forget. Keep track of what you are doing with your object between these two lines - the object can actually live for a long time, because "someone" will still become its owner. The release method at the end of the life cycle of this class does not mean that the object is now destroyed, but means: "I no longer care, my hands are clean"

So line by line:

 objectArray = [[parserDelegate objectArray] copy]; 

This is beautiful, I do not copy deeply. I am copying an array, which means that it allocates new memory for the array object, but not for the content. BUT, the copy sent to objectArray also sends a save to each object. In my example, I release my parserDelegate, which also releases its own Array object, decreasing saveCount for each object. If I had not made a copy here, the objects reached keepCount = 0 and would be destroyed. Thus, I have a new array with pointers to old objects, but they essentially become my objects, because the previous array is destroyed, and because of my save, I become the owner. Sorry if this says too much, but I really needed to focus in order to figure it out.

 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 } 

The question here was whether I should initialize the currentObject.name NSString property, because it will be filled shortly after the characters are found. Now it is interesting. When you initialize the entire object, its NSString properties are zero. Now, later, I do

 currentObject.name = currentChars; 

What launches the setter. This setter is defined as (non-atomic, persistent), which means that the new value is saved, the old value is freed and a pointer is assigned. It's funny, no matter if the previous value is initialized or not - if it is initialized, it will be released anyway, if it is zero, then nil can still accept the release (although I'm not 100% sure?) - nothing will happen. But, for the sake of correctness, I assume that the initial line should look like this:

 else if ([elementName isEqualToString:@"name"]) { currentObject.name = [NSString new]; // just alloc/init routine [self setParseChars:YES]; // just set the flag to make parse control easier } 

Now:

 [currentChars autorelease]; 

Must not be. This is a confusing design, and it was a bad decision. All that should be there is just an init string.

 [objectArray addObject:[currentObject copy]]; 

There is no need for copying. addObject will persist anyway. It makes no sense to create another distribution. That was my cause of my leaks.

Everything else is fine. Since I release currentChars right after setting the value for my object, it now saves it and becomes the owner, and I just release it “here” in the parser because I no longer need it (this will be highlighted in the next loop).

This can be confusing, but I can imagine that there will be other problems with non-standard memory, and even for experienced people, it may be a problem to put things in the right places. Maybe my story will help then.

0
source share

Static analyzer

In addition to: Cocoa Memory Programming Guide , Static Analyzer is an indispensable tool.

Project-> Project Settings-> Build-> Build Options-> Run Static Analyzer

Make sure it is checked.

It will tell you all the memory allocation errors you make. So, you’ll better understand how to create objects, errors with double auto-advertising, errors with double release, referring to released objects, etc.

I read the principles of memory management many times, but did not get it until I used a static analyzer.

Now I am better at it and get it right most of the time. A static analyzer, however, is still needed because it indicates errors and omissions.

Eiti

0
source share

All Articles