Copy an Array - Bad Design? - iphone

I learn objective-C from Stanford iTunes and i wonder how i should copy a NSMutableArray to NSArray without initialization. I mean:
Is this is correct? with "lazy initialization".
-(void)copyAnArray:(NSMutableArray*)listOfElements {
if(privateElementsLists == nil)
privateElementsLists = [[NSArray alloc] initWithArray:listOfElements copyItems:YES];
else
privateElementsLists = listOfElements;
}
is this a bad design?
I want to addobjects to mutable array in one class, and then when i'm finish copy entire NSMutableArray to NSArray.
And another question: Why i have to use copyItems:YES when I use initWithArray? And what's deep copy?

You can copy a mutable array to a new array with initWithArray: or this way:
privateElementsLists = [NSArray arrayWithArray:listOfElements];
then you are creating a new array where each of its elements is the same object that figures in the original array. If you write:
privateElementsLists = [NSArray arrayWithArray:listOfElements copyItems:YES];
then the new array have, for each element, a copy of the element in original array. They are not the same object but a copy. Of course, that objects have to be able to respond to copy.
You can even do this:
privateElementsLists = (NSArray*) listOfElements ;
Then the array is exactly the same as the original one. No new array here. But as you have casted it with NSArray pointer class, you can use it as if it is a NSArray instead of a NSMutableArray. As you know, every NSMutableArray is a NSArray (inherited class).

As Joseph DeCarlo stated, you don't need to copy NSMutableArray to NSArray if the only thing you do is to create the array in one place to use it somewhere else. For example this statement is valid:
NSArray* newArray = [NSMutableArray array];
Or in the code:
-(NSArray*)returnAnArray
{
NSMutableArray* editableArray = [NSMutableArray array];
[editableArray addObject:[[NSObject alloc] init]]; //an exemplary object added to the array
return editableArray;
}
That said, however, in some specific cases casting NSMutableArray to NSArray may not be safe, e.g. if the original array was stored in an instance variable. Adding or removing objects to/from that array may cause a crash if the returned array is enumerated at the same time. For example:
-(void)createArray
{
self->editableArray = [NSMutableArray array]; // instance variable: NSMutableArray* editableArray
}
-(void)addObjectToArray
{
[self->editableArray addObject:[[NSObject alloc] init]];
}
-(NSArray*)getArray
{
return self->editableArray;
}
-(void)enumerateArray
{
for(NSObject obj in [self getArray])
{
// do something with obj
}
}
If addObjectToArray is called at the same time as enumerateArray (e.g. from a background thread) the application will crash because the underlying array is changing while it is being enumerated. It doesn't matter that it was returned as NSArray*. In a case like this you would need to either add #synchronized to synchronize access to the same object by multiple threads, or copy the entire array with arrayWithArray: as suggested. Note, however, that the documentation doesn't say if arrayWithArray: is thread safe so I would add #synchronized around the call to arrayWithArray: anyway.

Related

Is it good practice to convert return value type from NSMutableArray to NSArray if return type of method is NSArray?

In the following code, I don't get any warning or compile error if I return NSMutableArray instead of NSArray which is the method's return type.
But is it good practice to convert return value to NSArray by using -copy method like the following code?
Or should I return NSMutableArray?
+(NSArray *)returnImutableArray {
NSMutableArray *arr = [[NSMutableArray alloc] init];
[arr addObject:#"a"];
[arr addObject:#"b"];
//return arr; // no warning
return [arr copy];
}
I think this is essentially personal preference. You incur a (probably small) performance penalty by copying the array before returning it. The upside is that you'll completely prevent mutation of the result further down the line. However, I don't usually go to the trouble. Because the declared return type of the method is an immutable NSArray, the compiler will warn you if you try to call one of NSMutableArray's mutation methods on it unless you go out of your way to prevent that (by casting to NSMutableArray).
So, in short, it's personal preference and I personally don't generally bother with the immutable copy.
This all depends on what you want to achieve with the array.
If you really don't want or need to modify the returned array, return it as an NSArray.
You could as well return it as an array, without copying it.
In both cases, don't forget the memory management.
You should never return anything with a retain count >0.
+(NSArray *)returnImutableArray {
NSMutableArray *arr = [[NSMutableArray alloc] init];
[arr addObject:#"a"];
[arr addObject:#"b"];
NSArray * returnArray = [NSArray arrayWithArray:arr];
[arr release];
//return arr; // no warning
return returnArray;
}
Or you could do so:
+(NSArray *)returnImutableArray {
NSMutableArray *arr = [[NSMutableArray alloc] init];
[arr addObject:#"a"];
[arr addObject:#"b"];
//return arr; // no warning
return [arr autorelease];
}
There's no need to copy the NSMutableArray as it extends the NSArray so your code will never break if you do so.
I return an immutable copy of interior mutable instances by default. Not just NSArray, but several other types.
I don't want mutable objects floating around, although it's not a problem that it mutates as long as you treat it as concrete.
I consider it a better default because it minimizes shallow copying if shared. That is, if you take the result and assign it to one or more objects which are copy properties, then all those actions must make concrete shallow copies, when they could effectively retain. This is the same reason you should favor copy for properties of these types, rather than retain when declaring properties.
In some cases, you just know its scope is limited and that the mutable won't get out -- in that case, I may not bother making the copy.

NSMutableArray is deleting all object with same string

I am using one NSMutableArray with same string object.
Here is the code
NSMutableArray *arr = [[NSMutableArray alloc]initWithObjects:#"hello",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",nil];
NSObject *obj = [arr objectAtIndex:2];
[arr removeObject:obj];
NSLog(#"%#",arr);
When i try to remove 3rd object of array, its removing all object with "hi" string.
I am not getting why its happening.
my doubt is while removing object, NSMutableArray match string or address.
It's because you're using removeObject which removes all objects that are "equal" to the one you pass in. As per this Apple documentation:
This method uses indexOfObject: to locate matches and then removes
them by using removeObjectAtIndex:. Thus, matches are determined on
the basis of an object’s response to the isEqual: message. If the
array does not contain anObject, the method has no effect (although it
does incur the overhead of searching the contents).
You're seeing the effects of literal strings here where each of those #"hi" objects will turn out to be the same object just added many times.
What you really want to do is this:
NSMutableArray *arr = [[NSMutableArray alloc]initWithObjects:#"hello",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",nil];
[arr removeObjectAtIndex:2];
NSLog(#"%#",arr);
Then you're specifically removing the object at index 2.
According to https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSMutableArray_Class/Reference/Reference.html
removeObject:
Removes all occurrences in the array of a given object.
which is exactly the behaviour you're seeing. If you want to remove the object at a particular position, you want removeObjectAtIndex:.
NSMutableArray *arr = [[NSMutableArray alloc]initWithObjects:#"hello",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",nil];
NSUInteger obj = [arr indexOfObject:#"hi"]; //Returns the lowest integer of the specified object
[arr removeObjectAtIndex:obj]; //removes the object from the array
NSLog(#"%#",arr);

How can i get Original order of NSDictionary/NSMutableDictionary?

i have created NSMutableDictionary with 10 keys.Now i want to access NSMutableDictionary keys in a same order as it was added to NSMutableDictionary (using SetValue:* forKey:* );
How can i achieve that ?
If you absolutely must use a dictionary container, you have to use a key that is sortable by the order in which you add key-value pairs. Thus, when creating your dictionary, you use a key that is an auto-incrementing integer or similar. You can then sort on the (integer) keys and retrieve the values associated with those keys.
If you do all of that, however, you may as well just use an NSMutableArray and add values to the array directly! It will be much faster and require less code. You just retrieve objects in order:
for (id obj in myArray) { /* do stuff with obj... */ }
NSMutableDictionary can't do that. Take a look at e.g. Matt Gallaghers OrderedDictionary.
I wrote a quick method to take a source array (of objects that are all out of order) and a reference array (that has objects in a desired (and totally arbitrary) order), and returns an array where the items of the source array have been reorganized to match the reference array.
- (NSArray *) reorderArray:(NSArray *)sourceArray toArray:(NSArray *)referenceArray
{
NSMutableArray *returnArray = [[NSMutableArray alloc] init];
for (int i = 0; i < [referenceArray count]; i++)
{
if ([sourceArray containsObject:[referenceArray objectAtIndex:i]])
{
[returnArray addObject:[arrReference objectAtIndex:i]];
}
}
return [returnArray copy];
}
Note that this is very fragile. It uses NSArray's containsObject: method, which ultimately will call NSObject's isEqual:. Basically, it should work great for arrays of NSStrings, NSNumbers, and maybe NSDates (haven't tried that one yet), but outside of that, YMMV. I imagine if you tried to pass arrays of UITableViewCells or some other really complex object, it would totally sh*t itself, and either crash or return total garbage. Likewise if you were to do something like pass an array of NSDates as the reference array and an array of NSStrings as the source array. Also, if the source array contains items not covered in the reference array, they'll just get discarded. One could address some of these issues by adding a little extra code.
All that said, if you're trying to do something simple, it should work nicely. In your case, you could build up the reference array as you are looping through your setValue:forKey:.
NSMutableArray *referenceArray = [[NSMutableArray alloc] init];
NSMutableDictionary *yourDictionary = [[ NSMutableDictionary alloc] init];
for (//whatever you are looping through here)
{
[yourDictionary setValue://whatever forKey:key];
[referenceArray addObject:key];
}
Then, when you want to loop over your items in the order they came in, you just
for (NSString *key in [self reorderArray:[myDict allKeys] toArray:referenceArray])
Actually you have a reference array in order manner then why you have to add to one more array.So i guess this approach is not good.Please consider my opinion.
Although #GenralMike 's answer works a breeze, it could be optimized by leaving off the unnecessary code as follows:
1) Keep an array to hold reference to the dictionary keys in the order they are added.
NSMutableArray *referenceArray = [[NSMutableArray alloc] init];
NSMutableDictionary *yourDictionary = [[ NSMutableDictionary alloc] init];
for (id object in someArray) {
[yourDictionary setObject:object forKey:someKey];
[referenceArray addObject:someKey]; // add key to reference array
}
2) Now the "referenceArray" holds all of the keys in order, So you can retrieve objects from your dictionary in the same order as they were originally added to the dictionary.
for (NSString *key in referenceArray){
//get object from dictionary in order
id object = [yourDictionary objectForKey:key];
}

iPhone SDK: How to copy the contents of a NSArray to NSMutable array?

I need to copy the contents of an NSArray to NSMutable array. In other words, I want to copy arrayCountryChoices to arraySearchResults. Any ideas????
//main data
NSArray *arrayCountryChoices;
//search results buffer
NSMutableArray *arraySearchResults;
//create data
arrayCountryChoices = [[NSArray alloc] initWithObjects:#"foo",#"bar",#"baz",nil];
//copy the original array to searchable array ->> THIS IS NOT WORKING AS EXPECTED
arraySearchResults = [[NSMutableArray alloc] arrayWithArray:arrayCountryChoices];
Thanks in advance.
it's either
[NSMutableArray arrayWithArray:anArray];
or
[[NSMutableArray alloc] initWithArray:anArray];
or
[anArray mutableCopy];
The code in your example doesn't work because you're calling arrayWithArray on an instance of NSMutableArray, but arrayWithArray is a class method.
As a general rule, initalization methods that start with init are instance methods, and those that start with the name of the class (array, etc.) are class methods. Class methods return autoreleased objects, while instance methods return retained objects.
You can also create empty mutable array and add objects to it using -addObjectsFromArray method
Please note that when using:
[NSMutableArray arrayWithArray:anArray];
or
[[NSMutableArray alloc] initWithArray:anArray];
you'll always get an array even if anArray was nil, but with:
[anArray mutableCopy];
you'll get nil if anArray is nil. So if you want to be sure that you actually get an array even if it may be empty, use one the former methods.
It's not working because arrayWithArray is a class method, not an instance method.
You can do [NSMutableArray arrayWithArray:arrayCountryChoices] to do what you need.
You could just call [arrayCountryChoices mutableCopy] to return a new mutable array with the contents of arrayCountryChoices.

Cocoa Touch: What to release, when to release it?

I'm trying hard to understand when and what I must relase in Cocoa Touch as it doesn't have garbage collection.
This code block is from apples iphone sample PeriodicElements and they release anElement and rawElementArray but not thePath, firstLetter, existingArray and tempArray?
I would have thought that at least tempArray and existingArray should be released.
Could some brainy person please explain to me why?
Thanks :)
- (void)setupElementsArray {
NSDictionary *eachElement;
// create dictionaries that contain the arrays of element data indexed by
// name
self.elementsDictionary = [NSMutableDictionary dictionary];
// physical state
self.statesDictionary = [NSMutableDictionary dictionary];
// unique first characters (for the Name index table)
self.nameIndexesDictionary = [NSMutableDictionary dictionary];
// create empty array entries in the states Dictionary or each physical state
[statesDictionary setObject:[NSMutableArray array] forKey:#"Solid"];
[statesDictionary setObject:[NSMutableArray array] forKey:#"Liquid"];
[statesDictionary setObject:[NSMutableArray array] forKey:#"Gas"];
[statesDictionary setObject:[NSMutableArray array] forKey:#"Artificial"];
// read the element data from the plist
NSString *thePath = [[NSBundle mainBundle] pathForResource:#"Elements" ofType:#"plist"];
NSArray *rawElementsArray = [[NSArray alloc] initWithContentsOfFile:thePath];
// iterate over the values in the raw elements dictionary
for (eachElement in rawElementsArray)
{
// create an atomic element instance for each
AtomicElement *anElement = [[AtomicElement alloc] initWithDictionary:eachElement];
// store that item in the elements dictionary with the name as the key
[elementsDictionary setObject:anElement forKey:anElement.name];
// add that element to the appropriate array in the physical state dictionary
[[statesDictionary objectForKey:anElement.state] addObject:anElement];
// get the element's initial letter
NSString *firstLetter = [anElement.name substringToIndex:1];
NSMutableArray *existingArray;
// if an array already exists in the name index dictionary
// simply add the element to it, otherwise create an array
// and add it to the name index dictionary with the letter as the key
if (existingArray = [nameIndexesDictionary valueForKey:firstLetter])
{
[existingArray addObject:anElement];
} else {
NSMutableArray *tempArray = [NSMutableArray array];
[nameIndexesDictionary setObject:tempArray forKey:firstLetter];
[tempArray addObject:anElement];
}
// release the element, it is held by the various collections
[anElement release];
}
// release the raw element data
[rawElementsArray release];
// create the dictionary containing the possible element states
// and presort the states data
self.elementPhysicalStatesArray = [NSArray arrayWithObjects:#"Solid",#"Liquid",#"Gas",#"Artificial",nil];
[self presortElementsByPhysicalState];
// presort the dictionaries now
// this could be done the first time they are requested instead
[self presortElementInitialLetterIndexes];
self.elementsSortedByNumber = [self presortElementsByNumber];
self.elementsSortedBySymbol = [self presortElementsBySymbol];
}
They create rawElementsArray by sending +alloc to the class, therefore this object is owned by the code in the sample above and must be released. Similarly with anElement. Note that thePath and tempArray are not created by sending +alloc, +new or -copy messages, therefore the calling code is not responsible for the lifetime of those objects. Please have a look at this collection of Cocoa memory management articles:
http://iamleeg.blogspot.com/2008/12/cocoa-memory-management.html
The reason you don't have to release tempArray is because it's been allocated and then autoreleased right away. Autorelease is a method of scheduling a release call sometime in the future, so that the caller of an API doesn't have to do any explicit releasing of the result.
Matt Dillard has provided a detailed explanation of Objective C's memory management strategy and has explained it much better than I can.
The convention is that when you create an object using a class method it should have been autoreleased. This means that at the end of the run loop when the autorelease pool is flushed these objects will be released. However, if you create something using +alloc] -init] or -copy, -mutableCopy or +new (which is the same as +alloc] -init]) then it will not have been autoreleased.
For example:
NSArray *array1 = [NSArray arrayWithObject:#"foo"];
NSArray *array2 = [[NSArray alloc] initWithObject:#"foo"];
Array1 will be autoreleased and you don't need to worry about it. Array2 will need to be manually released. Or alternatively you could do:
NSArray *array2 = [[[NSArray alloc] initWithObject:#"foo"] autorelease];
Which is pretty much what +arrayWithObject: does.
Of course this leads to an important consideration with the lifetime of instance variables. If you create the instance variable as with array2 then it will be fine as it has a retain count of 1. However, array1 will need to be retained otherwise it will be autoreleased at the end of the runloop, giving it a retain count of 0 and so it will be freed and you will be left with a dangling pointer.