Does mutableCopy make contained objects mutable? - objective-c

myArray is an NSArray of NSDictionary objects. Will [myArray mutableCopy] contain the original NSDictionary objects or NSMutableDictionary copies? Is there a simple way to make an immutable object graph completely mutable?

If you don't mind the amount of time required for a large object graph and you actually want deep copies of objects, you could serialize your object graph and then deserialize it. The easiest way to do this (assuming all your objects are Foundation Collection Objects) is to use the NSPropertyListSerialization class. Serialize your root object to data, then deserialize to your mutable root-level array using the NSPropertyListMutableContainersAndLeaves option. Your resulting root-level mutable array will be a deep-copy and all containers will be mutable. It's important to remember that this really will be a deep-copy, so if you change something in another container, that change won't be reflected in the original objects.
Here is a quick code example:
// Assumes the root-level object is an array, adjust as necessary
- (NSMutableArray*)deepMutableCopyOfArray:(NSArray*)array error:(NSError**)outError
{
NSError* error = nil;
NSData* serializedData = [NSPropertyListSerialization dataWithPropertyList:array format:NSPropertyListBinaryFormat_v1_0 options:0 error:&error];
if( !serializedData ) {
if( outError ) *outError = error;
return nil;
}
NSMutableArray* mutableCopy = [[NSPropertyListSerialization propertyListWithData:serializedData options:NSPropertyListMutableContainersAndLeaves format:NULL error:&error] retain];
if( !mutableCopy ) {
if( outError ) *outError = error;
return nil;
}
return mutableCopy;
}

Copies in Cocoa are generally shallow. This means that it only affects the top most object, in this case the array. You'll end up with a mutable array of immutable dictionaries. There is no one liner to make the entire thing mutable like you're asking.

The only way to do this is to iterate through the original array, create mutable copies of each object and replace the immutable objects in the mutable array with their mutable brethren. Whew, the word mutable has lost all meaning.
NSArray *mutableArray = [originalArray mutableCopy];
for (NSDictionary *dictionary in originalArray)
{
NSInteger index = [originalArray indexOfObject:dictionary];
NSMutableDictionary *mutableDictionary = [dictionary mutableCopy];
[mutableArray replaceObjectAtIndex:index withObject:mutableDictionary];
}
It should be clear that you can work down even further into the graph with nested for loops. Depending on the size of the array, you may require an autorelease pool to keep memory in check.

Related

Is a NSMutableDictionary in a NSDictionary still mutable?

The question is as simple as the title:
Is a NSMutableDictionary in a NSDictionary still mutable? Is the mdict mutable below?
NSMutableDictionary *mdict = [[NSMutableDictionary alloc] init];
NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:mdict, #"key", nil];
And, is a NSDictionary in a NSMutableDictionary still immutable?
Further, what if it's array/set instead of dictionary?
Absolutely! Mutability of an object does not change when you place it into a container.
When you place a mutable dictionary into another collection, mutable or immutable, that collection adds a reference to the mutable dictionary object, but it does not change it in any other way. Same goes for placing immutable objects into collections: collections reference these objects without changing their nature.
This remains true while your object is in memory. If you serialize it and then deserialize it back, the process of deserialization may remove mutability. For example, if you save NSMutableDictionary into NSUserDefaults and then read it back, you would get back an immutable dictionary.
Yes. Objects generally don't know when they're placed into a collection, so they can't change their behavior based on that. NSDictionary does copy its keys (precisely so you can change the original object without affecting the dictionary), but it just stores a normal reference to the value.
As long as you access your variables like so
NSMutableDictionary * tempDict = [mdict objectForKey: #"Key"];
NSMutableDictionary * tempDict2 = [arrayVar objectAtIndex: index];
The temp variables retain all the functionality as before

understanding mutable copy

Due to an example from Apress,for a search criteria,it has a soultion:it will firstly get a mutable copy and search the string and then will remove objects that are not in range of that string.
in the Mutable DeepCopy it has function as follow:
#implementation NSDictionary(MutableDeepCopy)
-(NSMutableDictionary *)mutableDeepCopy{
NSMutableDictionary *returnDict=[[NSMutableDictionary alloc]initWithCapacity:[self count]];
NSArray *keys=[self allKeys];
for(id key in keys)
{
id oneValue=[self valueForKey:key];
id oneCopy=nil;
if([oneValue respondsToSelector:#selector(mutableDeepCopy)])
oneCopy=[oneValue mutableDeepCopy];
else if([oneValue respondsToSelector:#selector(mutableCopy)])
oneCopy=[oneValue mutableCopy];
if(oneCopy==nil)
oneCopy=[oneValue copy];
[returnDict setValue:oneCopy forKey:key];
}
return returnDict;
}
#end
when I don't understand any code I will debug it,so I debugged it and in the beginnings when it wants to show the whole array,in the if statement it will go to oneCopy=[oneValue mutableCopy]; I want to know that why it will choose this selector and not the MutableDeep selector? I can't understand this function totally.what is the main purpose of this function?
for searching it has this function
-(void)handleSearchForTerm:(NSString *)searchTerm
{
NSMutableArray * sectionsToRemove=[[NSMutableArray alloc]init];
[self resetSearch];
for(NSString * key in self.keys)
{
NSMutableArray * array=[names valueForKey:key];
NSMutableArray *toRemove=[[NSMutableArray alloc]init];
for(NSString * name in array)
{
if([name rangeOfString:searchTerm options:NSCaseInsensitiveSearch].location==NSNotFound)
[toRemove addObject:name];
}
if([array count]==[toRemove count])
[sectionsToRemove addObject:key];
[array removeObjectsInArray:toRemove];
}
[self.keys removeObjectsInArray:sectionsToRemove];
[table reloadData];
}
and for reset the search and recovering whole it has this function below.
my problem is that in self.keys we removed objects with help of handleSearchForTerm but when we go to resetsearch it will return again those whole keys without any removal maybe my main problem is that I can't understand Mutablecopy and DeepMutableCopy.
-(void)resetSearch{
NSMutableDictionary *allNamesCopy=[self.allNames mutableDeepCopy];
self.names=allNamesCopy;
NSMutableArray *keyArray=[[NSMutableArray alloc]init];
[keyArray addObjectsFromArray:[[self.allNames allKeys]sortedArrayUsingSelector:#selector(compare:)]];
self.keys=keyArray;
}
They both have some compare & contrast features...
First comparing: Both are mutable, you can alter the object itself.
Second contrasting/differences:
MutableCopy: Similar to Call-by-Reference.
MutableDeepCopy: Similar to Call-by-Value.
A copy of a container (e.g. NSArray) copies only the pointers to objects and sends them retain.
A mutableCopy does the same thing, but the resulting container can be modified, i.e. Pointers can be added or removed.
A deep copy would also make copies of the individual elements.
Take for example an NSArray of NSMutableString instances. You cannot modify the array itself, but you can alter each mutable string. Now if you copy the array and modify one of the strings then the copied array also has the changes because it's pointers point to the same instances as the first array.
If you deep copy the array and alter a mutable string, then the new array elements are not modified. In fact copy always makes an immutable copy.

Creating local objects, prefrence or simply better?

Is it better to create a local object for later use like
NSDictionary *dic = [NSDictionary Dictionary];
or
NSDictionary * dic = nil;
Is it preference thing or is one better then the other?
it's not like 'the one is better', it's like 'the other is bad'.
If you're going to assign a new object to it later, initialize it to nil, else (you leak memory by losing the reference to the first object created by error.) - EDIT: no, you're not leaking memory (either because of the autorelease or the automatic reference counting, but anyway, that's an extra unneeded method call.) That is bad.
If it's a mutable collection, create it before you use it, else it will continue being nil and ignoring essentially all messages sent to it, which is also bad.
Conclusion: it's not a matter of preference - you must think logically and choose whichever is suited for the specific purpose you are using it for.
If you will use that object later, then you should instantiate it with the first option. If you will have to create an object in some if-else block where you will be reinitializing it with some custom values, then the second option is the way to go.
For example the first option:
NSMutableArray *arr = [NSMutableArray array];
for (int i = 0; i < 5; i++) {
[arr addObject:#"string"];
}
or
NSDictionary *dictionary = nil;
BOOL flag;
if (flag) {
dictionary = [NSDictionary dictionaryWithObject:#"string" forKey:#"myKey"];
}
else {
NSArray *objects;
NSArray *keys;
dictionary = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
}

Objective-C method returns NSDictionary instead of NSMutableDictionary. Why?

I am using the included method to return a pointer to a NSMutableDictionary, that is contained in an NSArray. However, the NSMutableArray (theOne) is being created as a non-mutuable NSDictionary. This is a problem because I want to modify the dictionary after retrieving it with this method.
- (NSMutableDictionary*)getMatFromBoutKey:(NSString*) boutKey
{
/*
* Returns the mat object with the provided boutKey.
* Returns nil if no mat has that boutKey.
*/
NSUInteger idx = [[event objectForKey:#"mats"] indexOfObjectPassingTest:
^ BOOL (NSMutableDictionary* obj, NSUInteger idx, BOOL *stop)
{
return [[obj objectForKey:#"boutKey"] isEqualToString:boutKey];
}];
if (idx == NSNotFound)
return nil;
else {
NSMutableDictionary* theOne = [[event objectForKey:#"mats"] objectAtIndex: idx];
return theOne;
}
}
Here's an image of the debugger stopped on a breakpoint right after the theOne is first referenced.
Why isn't theOne mutable? How can I return a pointer to the NSMutableDictionary so that I can modify it after I get the value returned to me?
Thanks!
I'm going to assume that you have a dictionary of arrays. Then that array contains a bunch of regular dictionaries. So when you pull it out of the array it is still a regular dictionary regardless of what you assign it to.
For instance, take the following code for example
NSDictionary *dict = [[NSDictionary alloc] init];
NSMutableDictionary *mutDict = dict;
mutDict will contain a regular dictionary because it has not properly been casted to a mutable dictionary.
either make sure when you create the array that is at [event objectForKey:#"mats"] that you put NSMutable dictionaries inside of it OR
use
NSMutableDictionary* theOne = [[[event objectForKey:#"mats"] objectAtIndex: idx] mutableCopy];
When taking the data out
In general, I think it's better practice to work with immutable objects when mutability isn't strictly necessary. Mutable objects use more memory, and of course have the potential of being accidentally changed. Maybe in the block are being changed to the enumerator(I'm not sure, but it is possible. For faster indexing). If you want to change a mutable object is via mutableCopy. or using a other method.
Is it inserted as mutable elsewhere in the code? If so it should return as mutable, if not you can send it the mutableCopy message to get a mutable copy (that has a reference count of 1, so be sure to release it when necessary).

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.