Objective-C: changing values of one NSMutableArray automatically change values of another NSMutableArray that is assigned to it - objective-c

I am initializing two NSMutableArrays namely:
NSMutableArray *firstArray = [[NSMutableArray alloc] init];
NSMutableArray *secondArray = [[NSMutableArray alloc] init];
firstArray has some values in it and secondArray has some values in it too.
Then I assigned firstArray to secondArray:
firstArray = secondArray;
Now if I make some changes in firstArray it will also affect secondArray. For example, if I replace or remove a value at certain index from firstArray then that value will also get removed from the secondArray.
So is there any way that changing in firstArray does not affect secondArray?

When you set firstArray = secondArray; you're actually setting their pointers to the same value, rather than setting the items in the first array to be the same as the items in the second array. This means that you have made firstArray and secondArray the exact same object, rather than having the exact same values. If you just want to have a copy of the second array into the first, you just need to specify that.
firstArray = [secondArray mutableCopy];
firstArray = [secondArray copy]; // non-mutable copy

The thing is that NSArray and NSMutableArray in Objective-C is reference type. So, by doing firstArray = secondArray; you actually saying that firstArray and secondArray are the same object. So, no matter how you will access it, thought firstArray pointer or secondArray pointer, you will be accessing exact the same object, so as modifying.
I think what you trying to achieve is to copy your values from fristArray into secondArray, it is possible to do by calling copy or mutableCopy
firstArray = [secondArray copy];

The problem you are facing is that both array is of reference type not value, so both point to same address, so that if you change the value of one it will effects another.
Initialize first array using initWithArray:copyItems: initializtion.
NSArray *firstArray = [[NSArray alloc] initWithArray:secondArray copyItems:YES];

Try initialising both arrays like this
NSMutableArray *firstArray = [[NSMutableArray alloc] init];
NSMutableArray *secondArray = [[NSMutableArray alloc] initWithArray:firstArray];
Hope this helps.

Related

mutable copy copies by reference, not value?

Apparently mutableCopy copies by reference, not value. Ie if I do this:
NSMutableArray arrayA = [arrayB mutableCopy];
then change values of arrayB, then arrayA's values will also be changed.
I think Java has a clone() method to copy by value.. is there an equivalent in objective c?
The mutableCopy method performs “shallow” copy. Each element of arrayA is a reference to an object that is also in arrayB. If you add elements to arrayA (or remove elements), arrayB will be unchanged, and vice versa. But since the elements of arrayA and arrayB reference the same objects, a change to one of those objects “shows up” in both arrays.
If you want a one-level deep copy of arrayB, you can do this:
NSMutableArray *arrayA = [[NSMutableArray alloc] initWithArray:arrayB copyItems:YES];
That will have this effect:
NSMutableArray *arrayA = [[NSMutableArray alloc] init];
for (id element in arrayB) {
[arrayA addObject:[element copy]]; //copies immutable objects to new array
}
To deep copy an array you need to use:
NSMutableArray *newArray = [[NSMutableArray alloc] initWithArray: oldArray copyItems:YES];
This performs a copyWithZone: on each object in the array
The regular [NSMutableArray copy] method will, as per Apple's documentation, return a "functionally independent object with values identical to the original at the time the copy was made." You should probably just use that.
To be totally sure that it is mutable, use [[NSMutableArray alloc] initWithArray:[otherArray copy]].

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];

Is this a correct declaration of an Array in Objective c?

NSArray *arr=[[[NSArray alloc]autorelease]autorelease];
Close, but no cigar.
If you want an autoreleased NSArray, you'd need to use:
NSArray *arr = [[NSArray alloc] init] autorelease];
That said, this will simply get you an empty immutable array, so you'll most likely want to populate it via one of the initWithObjects: style methods. (See the full NSArray class reference for more information.)
Or you can just declare it like this:
[NSArray array];
This gives you an autoreleased instance of the array.

Terminology question regarding looping thru an NSArray in Objective-C

When you have an NSArray and you want to evaluate and change the elements, you can't change the array from inside the loop. So, you create a mutable copy that can be changed.
code example:
NSMutableArray *bin = [NSMutableArray arrayWithObjects:#"0", #"1", #"2", #"3", #"4", #"5", #"6", #"7", nil];
NSMutableArray *list = [NSMutableArray arrayWithObjects:#"a1", #"b2", #"c3", #"e4", nil];
NSMutableArray *listHolder = list; // can't mutate 'list' within loop so create a holder
for (int i = 0; i < [list count]; i++) {
[listHolder replaceObjectAtIndex:i withObject:[bin objectAtIndex:i]];
}
What is that second array listHolder called? I mean, what term is used to refer to an array in this context.
This is perfectly valid:
NSMutableArray *bin = [NSMutableArray arrayWithObjects:#"0", #"1", …, #"7", nil];
NSMutableArray *list = [NSMutableArray arrayWithObjects:#"a1", …, #"e4", nil];
// NSInteger should be used instead of int
for (NSInteger i = 0; i < [list count]; i++) {
[list replaceObjectAtIndex:i withObject:[bin objectAtIndex:i]];
}
You're not allowed to change the array inside a for … in or NSEnumerate loop, but using an index is perfectly valid.
What troubles me is your misunderstanding of pointers.
If it were a loop in which you weren't allowed to mutate the array this wouldn't copy the array but only the pointer to the array, effectively modifying the array you're not allowed to. (I'm not even sure if this works.)
Instead of just copying the pointer
// can't mutate 'list' within loop so create a holder
NSMutableArray *listHolder = list;
make a true copy:
NSMutableArray *copy = [[list mutableCopy] autorelease];
In case I really have to make a copy I try to name it according to its content. For example:
NSMutableArray *views;
NSMutableArray *reorderedViews = [views mutableCopy];
// reorder reorderedViews
Sometimes it's hard to find a good enough name, then I usually just use nameCopy.
In this context listHolder would be called a copy.
Your code has a bug though. This line is not actually making a copy, it is only letting listHolder and list both reference the same array object:
NSMutableArray *listHolder = list;
This would be an actual copy:
NSMutableArray *listHolder = [list mutableCopy];
Make sure that you use mutableCopy and not just copy if you want the copy to be mutable. The copy method will return immutable variants on all mutable classes such as NSMutableSet, NSMutableDictionary, and so forth.
Also as others have noted it is only inside the for (item in collection) loop that the enumerated collection can not be mutated. In a normal for (;;) mutation is perfectly ok, but can lead to strange result if the number of items in the collection changes.
There is not specific stylistic or common name for this that is universally used, it is your code afterall, and if there appropriate terms for them use them.
Having said that generally if you don't have specific names in this sort of situation then people refer to the original list as the "source" (src) and the final list as "destination" (dst), just like in a memory blitting style operation.
A temporary mutable copy of the original NSArray would be how I would refer to it.

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