Order and release NSArray in Objective-C - objective-c

I am trying to sort an array of countries. This way works, but I can't figure out the way to release tmpArray. How do I release it and is there a better way of doing this?
// PUT COUNTRIES IN ARRAY
NSString *myFile = [[NSBundle mainBundle] pathForResource:#"Countries" ofType:#"plist"];
NSArray *tmpArray = [[NSArray alloc] initWithContentsOfFile:myFile];
tmpArray = [tmpArray sortedArrayUsingSelector:#selector(caseInsensitiveCompare:)];
arrayCountries = [[NSArray alloc] initWithArray:tmpArray] ;
// [tmpArray release];

Either -autorelease the one you alloc/init'd (because you're losing your reference to it when you replace it with the sorted array) or use another variable like 'sortedTmpArray'.
What you're currently doing is "create this object and assign it to tmpArray", then "create another array by filtering this one and assign it to tmpArray". At that point, you no longer have a pointer to the first array you created so there's no way to release it - it's leaked.
The solution is to place it in the autorelease pool when you create it or just use two separate pointers. Alternatively, you can create a mutable array the first time and use -sortUsingDescriptors: to sort it in place instead of creating two separate arrays.

Related

initWithArray vs initWithArray copyItems [duplicate]

This question already has answers here:
Deep copying an NSArray
(6 answers)
Closed 9 years ago.
I have a question what's the difference between these two methods on initializing an array?
I am assuming copyItems will provide a deep copy?
When would you use one versus the other?
Thank you!
The documentation for these methods is the first result (for me) with a little Google search.
initWithArray: copyItems:
has this documentation:
Initializes a newly allocated array using anArray as the source of data objects for the array.
Parameters
array
An array containing the objects with which to initialize the new array.
flag
If YES, each object in array receives a copyWithZone: message to create a copy of the object—objects must conform to the NSCopying
protocol. In a managed memory environment, this is instead of the
retain message the object would otherwise receive. The object copy is
then added to the returned array. If NO, then in a managed memory
environment each object in array simply receives a retain message when
it is added to the returned array.
whereas initWithArray: has this documentation:
Initializes a newly allocated array by placing in it the objects contained in a given array.
e.g.
Note: Since NSArray isn't mutable, my corresponding implementations aren't directly usable
`array2 = [[NSArray alloc] initWithArray:array1 copyItems:YES]`
//would correspond to:
array2 = #[
[array1[0] copy],
[array1[1] copy],
[array1[2] copy],
...
[array1[n] copy],
]
whereas
array2 = [[NSArray alloc] initWithArray:array1]
//would correspond to:
array2 = #[
array1[0],
array1[1],
array1[2],
...
array1[n],
]
//or
array2[0] = array1[0];
array2[1] = array1[1];
array2[2] = array1[2];
...
array2[n] = array1[n];
initWithArray: initializes a new array and places in it all the objects contained in a given array. This means that each object in the given array will receive a retain. Hence, if you edit an object in the new array, you will modify that object even in the given array. (see shallow copy)
On the other hand, initWithArray:copyItems:, if YES is passed as second argument, will provide a deep copy.
Beware that if you need to deeply copy an entire nested data structure then this approach will not suffice. (see the Apple documentation)
Example:
NSMutableString *s = [[NSMutableString alloc] initWithString:#"hello"];
NSArray *a = #[s];
NSArray *b = [[NSArray alloc] initWithArray:a];
[a[0] appendString:#" there"];
after these lines the arrays a and b will contain the mutable string "hello there"
NSMutableString *s = [[NSMutableString alloc] initWithString:#"hello"];
NSArray *a = #[s];
NSArray *c = [[NSArray alloc] initWithArray:a copyItems:YES];
[a[0] appendString:#" there"];
while after these lines the array c will contain the mutable string "hello" and the array a will contain the mutable string "hello there"

How to copy NSArray to another NSArray?

I have many different NSArrays, and according to the users choice I want one of them to be copied to a new NSArray. How do I copy one NSArray to another?
There can be several ways for this-
array1 = [array2 copy];
Use initWithArray method.
You can also use initWithArray:copyItems: method. (This if for NSMutableArray)
you can use the
NSArray *_newArray = [NSArray arrayWithArray:_oldArray];
or if you prefer better, you can use:
NSArray *_newArray = [[NSArray alloc] initWithArray:_oldArray];
(in that case the object of the first array won't be copied, that get only a retain front he second NSArray, you can remove the object from any array it won't affect the other array, but if you change any object in any NSArray it will be changed in the other one as well because there is both of the old and the new array is working with the same instance of the objects.)
if your plan is to make another instance of the old objects in the new array:
NSArray *_newArray = [[NSArray alloc] initWithArray:_oldArray copyItems:true];
if you are using the ARC, you won't need to do anything else, if you are not, in the case of both -initWithArray: or -initWithArray:copyItems: you should use the [_newArray release]; to release the array after you don't want to use anymore.
As well as
NSArray *newArray = [oldArray copy];
if you need to add/remove from the new array, the simplest way to make a mutable copy is:
NSMutableArray *editableArray = [oldArray mutableCopy];
The above functions both make shallow copies, for deep copy it's as #holex and #rishi mentioned
NSArray *newArray = [[NSArray alloc] initWithArray:oldArray copyItems:true];
NSMutableArray *editableArray = [[NSMutableArray alloc] initWithArray:oldArray copyItems:true];

Returning an NSArray without Leaking?

I have been struggling with the best pattern for returning an array from a static method.
In my static method getList (in the BIUtility Class), I am allocating an NSArray to return. in the return line, I do:
return [array autorelease];
Then in the calling method, I am allocating an array like this:
NSArray * list = [[[NSArray alloc] initWithArray:[BIUtility getList]] retain];
Later I release the list using:
[list release];
I think this is causing a memory leak as the retain is increasing the retain count one too many. However, if I do not do the retain, I get a Bad_Exec because it has already freed the class.
I feel like I am overthinking this and there must be a typical pattern. I have been looking all over the place and I cannot find a "best practice".
I appreciate your help.
You should replace:
NSArray * list = [[[NSArray alloc] initWithArray:[BIUtility getList]] retain];
With:
NSArray * list = [[BIUtility getList] retain];
This is because getList actually returns a pointer to the NSArray.
If it were a mutable array, however, you should say [[BIUtility getList] copy]; so that you don't accidentally mutate an array that another object has a reference to.
If you are curious, you were getting a memory leak because your original statement increments two counters, while you only release one later.
These parts of the statement increase counts:
[anObject]] retain]
[anClassname alloc]
[anObject copy] will also create an object with a count of 1.

Objective-C accessing / changing array elements in a multidimensional array (NSArray)

I'm trying to change a value in a multidimensional array but getting a compiler error:
warning: passing argument 2 of 'setValue:forKey:' makes pointer from integer without a cast
This is my content array:
NSArray *tableContent = [[NSArray alloc] initWithObjects:
[[NSArray alloc] initWithObjects:#"a",#"b",#"c",nil],
[[NSArray alloc] initWithObjects:#"d",#"e",#"f",nil],
[[NSArray alloc] initWithObjects:#"g",#"h",#"i",nil],
nil];
This is how I'm trying to change the value:
[[tableContent objectAtIndex:0] setValue:#"new value" forKey:1];
Solution:
[[tableContent objectAtIndex:0] setValue:#"new val" forKey:#"1"];
So the array key is a string type - kinda strange but good to know.
NSMutableArray *tableContent = [[NSMutableArray alloc] initWithObjects:
[NSMutableArray arrayWithObjects:#"a",#"b",#"c",nil],
[NSMutableArray arrayWithObjects:#"d",#"e",#"f",nil],
[NSMutableArray arrayWithObjects:#"g",#"h",#"i",nil],
nil];
[[tableContent objectAtIndex:0] replaceObjectAtIndex:1 withObject:#"new object"];
You don't want to alloc+init for the sub-arrays because the retain count of the sub-arrays will be too high (+1 for the alloc, then +1 again as it is inserted into the outer array).
You're creating immutable arrays, and trying to change the values stored in them. Use NSMutableArray instead.
You want either NSMutableArray's insertObject:atIndex: or replaceObjectAtIndex:withObject: (the former will push the existing element back if one already exists, while the latter will replace it but doesn't work for indices that aren't already occupied). The message setValue:forKey: takes a value type for its first argument and an NSString for its second. You're passing an integer rather than an NSString, which is never valid.
Sorry for responding 1 and half years old question :D
I got the same problem, and at last I solved it with counting the elements, then do addObject to push to the array element

Assigning values to Instance variables in Objective C

The function I'm looking at:
-(void)viewDidLoad {
NSBundle *bundle = [NSBundle mainBundle];
NSString *plistPath = [bundle pathForResource:#"statedictionary" ofType:#"plist"];
NSDictionary *dictionary = [[NSDictionary alloc] initWithContentsOfFile:plistPath];
self.statesZips = dictionary;
[dictionary release];
NSArray *components = [self.stateZips allKeys];
NSArray *sorted = [components sortedArrayUsingSelector:#selector(compare:)];
self.States = sorted;
NSString *selectedState = [self.states objectAtIndex:0];
NSArray *array = [stateZips objectForKey: selectedState];
self.zips = array;
}
Why is an NSDictionary allocated, then assigned to a pointer called *dictionary, and then assigned to the instance variable stateZips? Why not allocate it and assign it directly to the instance variable and save memory of creating and releasing another NSDictionary? The same methodology is always followed, including later in this function with the NSArray...
NSDictionary *dictionary = [[NSDictionary alloc] initWithContentsOfFile:plistPath];
self.statesZips = dictionary;
[dictionary release];
Also, this sorting puts the keys from a hash table (dictionary) in alphabetical order. I'm not sure I understand this line:
NSArray *sorted = [components sortedArrayUsingSelector:#selector(compare:)];
No one seems to have addressed the fact that the line
self.statesZips = dictionary;
is not directly an instance variable assignment. stateZips is a property, and so that line of code calls the setStateZips: method. That method retains or copies the dictionary, so unless the viewDidLoad method intends to use it again for some purpose, it's not needed any longer. That makes it OK to release it.
The previous line:
[[NSDictionary alloc] initWithContentsOfFile:plistPath];
allocates an object. That makes it your responsibility to release it once you don't need it any more. After assigning it to the statesZips property, it's no longer needed, so it's released and you shouldn't use dictionary any more. You'll notice that later code only refers to self.stateZips, not dictionary.
In the case of the NSArray later in the method, viewDidLoad does not allocate the object, so that method is not responsible for calling release on it. The rule of thumb is that if you alloc it, you're responsible for making sure it gets released. Otherwise, it's not your problem.
Sorting the array uses the sortedArrayUsingSelector: method. A selector identifies a method in Objective-C. And the #selector is the literal syntax for selectors (kind of like how #"" is the literal syntax for NSString objects). So, what that code says, is "give me an array where the objects in components are sorted, and use the compare: method to compare each object when you do the sort. When it sorts the array, it will call compare: on the objects in the array to determine how to put them in order.
The statesZips property is probably retained, that's the reasoning.
When the NSDictionary is first allocated, its retain count is 1. When it's assigned to statesZips, the retain count becomes 2. When it's released, the retain count drops to 1, which is usually the desired outcome.
Note that the code below would have produced (almost) the same result:
self.statesZips = [NSDictionary dictionaryWithContentsOfFile:plistPath];
because dictionaryWithContentsOfFile returns an autoreleased object.
As a convention, class methods like [NSDictionary dictionary] return autoreleased objects (which automatically get released after some time), while the usual alloc-init method (as in [[NSDictionary alloc] init]) return retained objects.
I suggest you read the Memory Management Programming Guide for Cocoa for further information.
EDIT: I must have missed the last part of your question when I first read it, but Barry has already answered that part.
This code uses reference-counted memory management (not the automatic garbage collection memory management available in Objective-C 2.0 on OS X). When any object (in this case, the NSDictionary and the NSArray) are alloc'd, the caller is responsible for calling -release on that instance. Failing to call release causes a memory leak. The code could have been written as
self.statesZips = [[[NSDictionary alloc] initWithContentsOfFile:plistPath] autorelease];
but at the expense of less explicit memory management (relying on NSAutoreleasePool to release the alloc'd instance at the end of the event loop iteration.
the call
[components sortedArrayUsingSelector:#selector(compare:)];
returns an array of whose elements come from components but according to the return value of calling [elem1 compare:elem2] to compare two array elements.