To return a NSArray or NSDictionary, I have seen most people use the below implementation and this is also what some books suggest. (iOS Development A Practical Approach - )
OPTION 1
-(NSArray*)listOfStudents{
NSMutableArray *temp = [[NSMUtableArray alloc] init];
//Add elements to the array
//
//
//
NSArray *students = [NSArray arrayWithArray:temp];
return students;
}
-(void)viewWillAppear{
self.studentsList = [self listOfStudents];
}
But can this same be done by the below way also?
OPTION 2
-(NSArray*)newListOfStudents{
NSMutableArray *temp = [[NSMUtableArray alloc] init];
NSArray *students = [[NSArray alloc]initWithArray:temp];
[temp release];
//Add elements to the array
//
//
//
return students;
}
-(void)viewWillAppear{
NSArray *array = [self newListOfStudents];
self.studentsList = array;
[array release];
}
Assume these methods are called in the main thread itself.
Interms of memory usage , I think that the second option is good, because it does not create autoreleased objects, because they are released only at when the autorelease pool is drained.
I assume that the main autorelease pool is drained only when the app quits. So if the method in OPTION 1 is used many times ,(since they are getting called in ViewWillAppear) I think that many lists will be in autorelease pool being released only when the app quits.
So is the OPTION 2 approach the better approach?
UPDATE:
I have updated the viewWillAppear implementation for better clarity.
I think in the second example you meant to call
self.studentsList = [self newListOfStudents];
In case that studentsList is a retained property, this would leak now.
Also, that temp array in both examples is just useless overhead. In the second example it's plain nonsense.
The cleanest solution is
-(NSArray *)listOfStudents {
NSMutableArray *list = [NSMutableArray array];
// Add things to array
return list;
}
Two more advices:
1) you might run the static analyzer over your code, which will point to memory issues.
2) if you feel more confident with memory management, switch over to ARC.
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.
I have a section of my code which calls a method returning an NSMutableArray like so:
+(NSMutableArray *)method {
NSMutableArray *rgb = [[NSMutableArray alloc] initWithObjects:....., nil];
return rgb;
}
It gives me a leak every time it's called. Putting [rgb release]; after return doesn't appear to work. Putting it before return makes my app crash. Also putting in an autorelease makes my app crash. Suggestions?
+(NSMutableArray *)method {
NSMutableArray *rgb = [[NSMutableArray alloc] initWithObjects:....., nil];
return [rgb autorelease];
}
Alternatively:
+(NSMutableArray *)method {
NSMutableArray *rgb = [NSMutableArray arrayWithObjects:....., nil];
return rgb;
}
IF this still crashes, then the problem is most likely outside of that method, not within.
But this gives me a leak every time
it's called. Putting a [rgb release];
after return doesn't appear to work.
putting it before return makes my app
crash. Also putting in an autorelease
makes my app crash. Suggestions?
You need to read the Cocoa Memory Management Guidelines. It makes it quite clear that such a method as that must autorelease the returned object:
+(NSMutableArray *)method {
NSMutableArray *rgb = [[NSMutableArray alloc] initWithObjects:....., nil];
return [rgb autorelease];
}
Or, alternatively:
+(NSMutableArray *)method {
NSMutableArray *rgb = [NSMutableArray arrayWithObjects:....., nil];
return rgb;
}
An over-retain (like in your code) will not cause a crash (at least not until you run out of memory). If you have a crash, it is most likely because your are mismanaging memory in other ways.
Try using Build and Analyze and fixing all of the issues it identifies. If it still crashes, post the backtrace of the crash if you can't figure out why.
I have a cocoa 'category' for adding inflections (pluralize, singularize, etc.) to NSString. The code requires loading a set of regular expression rules and exceptions from a PLIST into dictionaries and arrays, as well as adding manual exceptions from code. I need a way to persist these data structures (as class members) between multiple calls to the inflection code (all instance methods). I attempted:
+ (NSMutableArray *)uncountables
{
static NSMutableArray *uncountables = nil;
if (uncountables == nil) uncountables = [NSMutableArray array];
return uncountables;
}
However, it appears to fail occasionally. Does a good way of doing this exist? I don't want to subclass NSString if possible. Thanks.
[NSMutableArray array];
returns an autoreleased array. Use this instead:
[[NSMutableArray alloc] init];
I think this code is OK. I use the same thing a lot for singletons. But be aware that it is not thread safe this way. Maybe you calling it from different threads?
As drawnonward already mentioned, [NSMutableArray array]; returns an autoreleased array. But I don't think, it's a good idea to return non-autoreleased array, because it contradicts with Cocoa memory management conceptions - only alloc, copy and new should be released manually. All other initializations are autoreleased.
So, you should just use
interface:
NSArray *a;
...somewhere in a code...
a = [[NSString uncountables] retain];
...
- (void)dealloc {
[a release];
}
to get properly retained/released objects.
This is probably a completely stupid question, but i'm pretty new at objective-C and programing in general.
i'm trying to make an array of arrays but can't manage to make it work :
#interface ArraysAndDicts : NSObject {
NSMutableArray * mySimpleArray;
NSMutableArray * myComplicatedArray;
}
the implementation :
-(void)generateValueForArrayOfArrays {
[self generateValueForArray];
//this generates an array with 5 elements 'mySimpleArray'
[myComplicatedArray addObject:mySimpleArray];
NSMutableArray * mySecondaryArray = [[NSMutableArray alloc] init];
[mySecondaryArray addObject:#"twoone"];
[mySecondaryArray addObject:#"twotwo"];
[myComplicatedArray addObject:mySecondaryArray];
(i edited out all the NSLogs for clarity)
When running my app, the console tells me :
mySecondaryArray count = 2
mySimpleArray count = 5
myComplicatedArraycount = 0
So, i know there are other ways to make multidimensional arrays,
but i'd really like to know why this doesn't work.
Thank you.
It looks like you're never creating myComplicatedArray. This means that
[myComplicatedArray addObject:mySimpleArray];
is actually
[nil addObject:mySimpleArray];
Sending messages to nil simply has no effect in Objective-C, so nothing happens. When you ask for the count of the array, you're effectively sending [nil count], which will return 0 in your case. You can find more information about sending messages to nil here.
Also, when you're doing
NSMutableArray * mySecondaryArray = [[NSMutableArray alloc] init];
[mySecondaryArray addObject:#"twoone"];
[mySecondaryArray addObject:#"twotwo"];
mySecondaryArray will leak. You should release it, either with autorelease
NSMutableArray * mySecondaryArray = [[[NSMutableArray alloc] init] autorelease];
...
or release, whichever is appropriate.
NSMutableArray * mySecondaryArray = [[NSMutableArray alloc] init];
...
[mySecondaryArray release];
We can't see how myComplicatedArray is initialised. Perhaps it's nil.
Did you initialize myComplicatedArray somewhere?
myComplicatedArray = [[NSMutableArray alloc] init];
[self generateValueForArray];
// etc...
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.