I am a little confused about releasing memory for array elements that are shared by multiple arrays. Here's the scenario:
Manager class has an instance variable NSMutableArray* mgrArray
Helper class has an instance variable NSMutableArray* helperArray.
Manager's init method:
NSMutableArray* mgrArray = [[[NSMutableArray alloc] init] autorelease];
for (int i=0; i<10; i++) {
Food *f = [[[Food alloc] initWithType:#"Fruit"] autorelease];
[mgrArray addObject:f];
}
Helper's init method:
NSMutableArray* helperArray = [[[NSMutableArray alloc] init] autorelease];
The manager object passes some of the mgrArray elements to Helper class to store for Helper's own access purposes (say for efficiency). Some Manager method that does this:
Food *e1 = [mgrArray objectAtIndex:3];
Food *e2 = [mgrArray objectAtIndex:5];
Food *e3 = [mgrArray objectAtIndex:7];
[helper. helperArray addObject:e1];
[helper. helperArray addObject:e2];
[helper. helperArray addObject:e3];
Question 1: when adding e1 to helperArray, should it be copied or retained or is it alright as written above?
Question 2: who should release the memory of the food objects and how?
If you put an object in an array, it retains it. If you remove an object from an array, the array releases it.
It is that simple; ownership -- retains -- should always be bounded by lines of encapsulation.
If you need to ensure that an object remains alive when you are using it, retain it. The one edge case is if you do something like:
foo = [someArray objectAtIndex: 0];
[someArray removeObjectAtIndex: 0];
At that point, foo may have been released. To fix:
foo = [someArray objectAtIndex: 0];
[foo retain];
[someArray removeObjectAtIndex: 0];
.. do stuff ..
[foo release];
Question 1: when adding e1 to
helperArray, should it be copied or
retained or is it alright as written
above?
Simply add e1 to the helperArray. Done.
Question 2: who should release
the memory of the food objects and
how?
When the array is released to the point of being dealloc'd, all objects contained within the array will be released (but not necessarily deallocated unless nothing else holds a retain).
An object will be retained when added to an array and released when removed from an array.
If you are using an autoreleased object there is nothing else to do.
If you are using a regular object, you can release it after it is added to the first array.
Related
I made simple experiment and found some strange behavior. Here some code - part of long method with ARC enabled:
MKObject *obj = [[MKObject alloc]init];
NSMutableArray *temp = [[NSMutableArray alloc]init];
[temp addObject:obj];
obj = nil;
temp = nil;
//here deallocating is called on obj (MKObject)
//other stuff
but if I change NSMutableArray to NSArray and literal initialisation
NSArray *temp = #[obj];
deallocating executed before autoreleasepool closed, not after setting nil to all references. Did I missed something ?
A few observations:
In your first example, neither the MKObject nor the NSMutableArray is an autorelease object, so these objects will be deallocated immediately, not waiting for the autorelease pool to drain:
MKObject *obj = [[MKObject alloc] init];
NSMutableArray *temp = [[NSMutableArray alloc] init];
[temp addObject:obj];
obj = nil;
temp = nil;
In your second example, the NSArray is an autorelease object, so the NSArray (and therefore, the MKObject) will not be deallocated until the autorelease pool is drained.
MKObject *obj = [[MKObject alloc] init];
NSArray *temp = #[obj];
obj = nil;
temp = nil;
To understand why the array literal, #[], creates an autorelease object, one should note that it expands to +[NSArray arrayWithObjects:count:]. Autorelease objects are created whenever you instantiate an object with any method other than using alloc followed by an init (whether init, or one of the permutations, such as initWithObjects:).
As you observed, when an app creates autorelease object, the object will not be immediately be deallocated, but it will when the autorelease pool drains. Since we generally yield back to the runloop quickly (at which point the pool will be drained), the choice of autorelease objects or non-autorelease objects has little practical impact in simple cases. But if the app, for example, has a for loop in which it creates many autorelease objects without yielding back to the runloop, it could be problematic (especially if MKObject was large or you were doing this many times). For example:
for (NSInteger i = 0; i < 100; i++) {
MKObject *obj = [[MKObject alloc] init];
NSArray *temp = #[obj];
// Note, because they are local variables which are falling out of scope, I don't have to manually `nil` them.
}
Because we are instantiating autorelease NSArray objects in this example, the above would keep all 100 arrays and objects in memory until you yielded back to the runloop and the autorelease pool had a chance to drain. This means that the app's "high water mark" (the maximum amount memory it uses at any given time), would be higher than it might need to be. You could remedy this by either:
use a non-autorelease object (such as by using alloc/init) instead of using the array literal:
for (NSInteger i = 0; i < 100; i++) {
MKObject *obj = [[MKObject alloc] init];
NSArray *temp = [[NSArray alloc] initWithObjects:obj, nil];
}
or
by introducing your own, explicitly declared #autoreleasepool:
for (NSInteger i = 0; i < 100; i++) {
#autoreleasepool {
MKObject *obj = [[MKObject alloc] init];
NSArray *temp = #[obj];
}
}
In this final example, the autorelease pool will be drained for each iteration of the for loop, resolving any challenges with autorelease objects that would otherwise have their deallocation deferred until the end of the loop.
One final caveat: In general, methods that begin with alloc and init (or a variation of the init method), will not generate autorelease objects, whereas all other methods, such as arrayWithObjects:count: will generate autorelease objects. One notable exception is the NSString class, which due to internal memory optimizations, does not conform to this rule. So, if you have any doubt, you can employ your own manual #autoreleasepool if you are repeatedly instantiating and releasing objects, and you are unsure as to whether the objects are autorelease objects or not. And, as always, profiling your app with the Allocations tool in Instruments is a good way of observing the app's high water mark. For an illustration of how these various techniques can impact the memory usage of your app, see https://stackoverflow.com/a/19842107/1271826.
The array was being retained by the autorelease pool. As described in Clang's Objective-C Literals documentation, the array literal syntax expands to a call to +[NSArray arrayWithObjects:count:], which creates an autoreleased array.
A couple of things I see, though I'm not entirely clear on the question so I can't say which applies:
Adding an object to an NSArray or NSMutableArray increments the object's retain count.
In the first instance, you manually instantiate obj, which gives it retain count 1.
Adding it to the NSMutableArray makes its retain count 2.
In this case, obj = nil decrements retain to 1;
temp = nil tells the array to handle releasing its contents. Those w/retain count 0 get dealloc'd immediately.
In the 2nd instance with #[] literal creation, the literal syntax under the hood creates an autoreleased object using the method arrayWithObjects: count:. When this is no longer needed it goes to the autorelease pool for eventual deallocation.
It isn't an issue of the objects IN the array but the way the arrays themselves were created.
Edited my original response to address comments below - I was confusing the issue.
So I had this code, and it did not work:
for (NSDictionary *item in data){
[self.resultsArray addObject:item];
}
self.resultsArray is nil. But then I changed it to this:
NSMutableArray *myDataArray = [[NSMutableArray alloc] init];
for (NSDictionary *item in data){
[myDataArray addObject:item];
}
self.resultsArray = myDataArray;
[myDataArray release];
and now it worked. self.resultsArray is now populated
So I'm a beginner in Objective C and I was wondering why can I not just directly use it in the property's addObject. Why did I have to create another mutable array, populate it, assign it to the resultsArray property and release the mutable array I made?
Thanks in advance!
EDIT: Also, in a lot of books I've been working on, this is done a lot.
simple answer
You didn't initialize self.resultArray before adding objects to it. It is just a pointer to the value which is nil until you alloc it.
self.resultArray = [[NSMutableArray alloc] init]; before adding objects to it will solve the issue.
However, this way of alloc'ing will create a memory leak, therefore it is not shown in books and examples. Memory leak can happen if the self.resultArray property is marked as retain and by calling alloc it will be retained 2 times.
If self.resultsArray is nil, then [self.resultsArray addObject:item] will NOT add an object to the array, it will just do nothing (because the array will be nil by default, and sending messages to nil is a no-op in Objective-C). When you create a mutable array as a local variable, you can add things to it — then if you assign it to the property, well, everything works as you expect and self.resultsArray will no longer be nil.
Typically when you have properties like this, you'd set them up in your init method:
- (id)init {
// ...
self.resultsArray = [NSMutableArray array];
// or access the ivar directly:
// _resultsArray = [[NSMutableArray alloc] init];
// ...
}
Then as soon as your object is initialized you'll be able to add things to the array. Again, if you don't do this, it will be nil by default, and [self.resultsArray addObject:item] will have no effect.
Chances are you are not initializing the array (I'm going to assume myDataArray is an NSMutableArray).
In your init method, call myDataArray = [NSMutableArray array]; and it'll work
The important thing to note is that you're not creating another mutable array as you didn't have an array to start with. Merely declaring a property or variable does not create an object to go along with it. That's why self.resultsArray starts out as nil.
The working code you have is designed to allow you to explicitly release the array as you are retaining it twice: once when you alloc it and once when you assign it to your property. You only want one of those retains, so you release once.
You could just do:
self.resultsArray = [[NSMutableArray alloc] init];
[self.resultsArray release];
for (NSDictionary *item in data){
[self.resultsArray addObject:item];
}
This is less code, but it's not as clear. Clarity is important.
If I have a 2 dimensional NSMutableArray eg,
board = [[NSMutableArray alloc] initWithCapacity:boardHeight];
for (int y = 0; y < boardHeight; y++) {
NSMutableArray *row = [[NSMutableArray alloc] initWithCapacity:boardWidth];
for (int x = 0; x < boardWidth; x++) {
[row insertObject:#"A string"];
}
[board insertObject:row atIndex:y];
[row release];
}
and I do
[board release];
Does that recursively release the array? Or do I have to manually go into the array and release each row?
If it does, and the object inserted into each row were a custom object, is there anything special I have to think about when writing the dealloc method for the custom object?
Everything will just work fine. The array will retain the objects when they are added and released when removed or the array is deallocated.
No extra work on that part.
When board is ready to be deallocated, it will send the release message to each object in itself. Even if those objects are NSArrays, they will still be released; and if those arrays are now ready to be deallocated, they will send release to their members, etc.
Your class should implement dealloc normally -- release any ivars that you hold a strong reference to before calling [super dealloc].
I have a newbie question regarding when to release the elements of a NSArray. See following pseudo code:
NSMutalbeArray *2DArray = [[NSMutableArray alloc] initWithCapacity:10];
for (int i=0;i<10;i++) {
NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:5];
for (int j=0;j<5;j++) {
MyObject *obj = [[MyObject alloc] init];
[array addObject:obj];
[obj release];
}
[2DArray addObject:array];
[array release];
}
// use 2DArray to do something
[2DArray release]
My question here is, when I release 2DArray, do I need to explicitly release each of its element (array) first? Also, before I release the "array" object, do I need to release each of its element (MyObject) first?
I am new to Objective C. Please help. thanks.
No, you don't need to tell each object to be released. When you send a release method to an NSArray, it automatically sends a release method to each item inside first.
So in your case, you send [2DArray release]. This automatically sends [array release] to every other array, which sends [obj release] to each object inside each array.
You don't need to release the kept objects. NSArray retains them when you add, and releases them when released. So if you allocate, add to the array, then release, the object in the array will have the retain count of 1. Once the array is freed, the object is released, therefore freed.
When an object is created, it has a retain count of 1. Whenever a object is added to an array, its retain count is increased (in this case to 2). After adding to the array, your code release its hold of the object, dropping its retain count by 1 (to 1 in this case). Then when you release the array, it calls release on everything in it dropping their retain counts by 1 (to 0 in this case). When retain count hits 0 the object is deallocated.
Your code looks correct from a memory management stand point.
What is the advantage of doing this:
NSArray *array = [[NSArray alloc] initWithObjects:#"Year", "#Capital", ..., nil];
self.hintArray = array;
[array release];
Instead of assigning directly to my class variable like this:
self.hintArray = [[NSArray alloc] initWithObjects:#"Year", "#Capital", ..., nil];
Why do we create a temporary local object then release it instead of just assigning to our class variable?
Others have already pointed out the memory issues, but here is the best way to do it in a single step:
self.hintArray = [NSArray arrayWithObjects:#"Year", "#Capital", ..., nil];
The convenience class method +arrayWithObjects returns an array that has already been autoreleased, so you simply don't need to worry about it any more. Your property accessor will take care of copying or retaining it. (assuming, of course, that your hintArray property is set up as a retain or copy property).
You could, but you have to remember to release it once before moving on. The assignment to self.hintArray (assuming it is a synthesized setter that retains on set) will bump the retainCount:
NSArray *array = [[NSArray alloc] initWithObjects:...]; // retainCount is 1
self.hintArray = array; // retainCount is 2
[array release]; // retainCount is 1
and:
self.hintArray = [[NSArray alloc] initWithObjects:...]; // retainCount is 2:
// one for the alloc
// and one for the assign
[self.hintArray release]; // retainCount is 1
Because in the Objective-C reference counted memory management scheme the creation of the array will increment the reference count and if you do not store the return value in a variable you can send a release message to you will have no way to decrement that count and will introduce a memory leak.