addEntriesFromDictionary: a true copy method? - objective-c

I am trying to understand exactly what is going on with this method, as noted in the Apple docs:
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSMutableDictionary_Class/Reference/Reference.html
If I create an NSMutableDicationary and use addEntriesFromDictionary: to fill it, can I do anything I want to this mutable dictionary without affecting the original immutable dictionary from where these items came?

The original dictionary will not be modified. However, if the keys or values of the original dictionary are themselves mutable in some way (e.g. they're instances of UIView or NSMutableArray) and you modify them, the changes will be reflected in the original dictionary.
To avoid that, make a deep copy of the original dictionary before adding it to the new dictionary:
NSDictionary *deepCopy = [[NSDictionary alloc] initWithDictionary: original copyItems: YES];
if (deepCopy) {
[destination addEntriesFromDictionary: deepCopy];
[deepCopy release];
}

Yes, modifications you make to the new dictionary will not affect the old one. Any changes you make to the objects inside the dictionary will affect those inside the original dictionary, though. They are the same same objects, after all. As the documentation says:
Each value object from otherDictionary is sent a retain message before being added to the receiving dictionary. In contrast, each key object is copied ... and the copy is added to the receiving dictionary.

You can check for yourself by logging the addresses of the keys and values. My guess is that it copies the keys, as is the standard NSDictionary behavior, and simply retains the values. You can mutate the dictionary (which comprises just the key->value mappings) all you want, but if you mutate the objects that are its values, you'll be mutating those objects everywhere.
EDIT: Logging a test case as suggested indeed shows that is the behavior. The copied key will in fact be the same as the original key for the common case of an immutable string key.

Related

When to and when not to use (mutable) copies in assignments (Objective C)

I am new to Objective C and I had no idea that in an NS(Mutable)Dictionary I must use (mutable)Copy for an assignment like this:
dict[#"backup"] = dict[#"myList"];
Using debugging I found out that the assignment must be done like this:
dict[#"backup"] = [dict[#"myList"] mutableCopy];
Now the question is: how do I know that I must use copies (vs references) and for which type of objects?
Thank you!
There is nothing that you must do.
A dictionary contains key-value pairs. For example, for your code to work, dict contains some object as the value for the key "myList". No idea what that object is. You can make three different assignments, and each is perfectly valid but does something different:
dict [#"backup"] = dict [#"myList"];
stores the same object that is already there under the key myList under the key backup as well. If the object is mutable, and someone modifies the object, then the object under each key is modified, because it is the same object.
dict [#"backup"] = [dict [#"myList"] copy];
"copy" is interesting. Usually it will create a copy of the object, so you have two objects, an old one and a new one. If the original is mutable, then the copy will be immutable. But if the original is immutable, then the OS assumes that there is no point in making a copy, so copy will give the original object. Anyway, dict [#"backup"] will be an immutable object that cannot be affected by modifications to dict [#"myList"], either because it is not the same object, or because dict [#"myList"] cannot be modified.
dict [#"backup"] = [dict [#"myList"] mutableCopy];
This makes a mutable copy of the original and stores it. It is definitely not the same object. And it can be modified.
It really depends on what you want to achieve. There is no right or wrong here.
Simply it depends on the use of the assigned element. If you are going to change its content it must be mutable. If you are just reading it, don't make it mutable.

NSDictionary + ARC + copy vs reference

These are probably are pretty simple YES|NO type questions.
I have some NSDictionaries containing other NSDictionaries. Let's say NSDictionary_A and NSDictionary_B. These persist for the life of the app.
The NSDictionaries contained in NSDictionary_A are passed by reference to various objects:
track.instrument = [NSDictionary_A objectForKey:#"Blue"];
Later it gets changed:
track.instrument = [NSDictionary_A objectForKey:#"Red"];
So first question: The #property instrument is synthesized + retained as strong so does the setter for instrumentset the current value of instrument to nil before setting the new value, and if so, does this affect the source of the reference in NSDictionary_A - in other words, set the reference to nil'? Sounds wrong just writing it out.. so I think the answer is NO here. Also, it probably doesn't matter that the #property instrument is stored as weak or strong since the reference in NSDictionary_A1 persists for the app life but since it is a pointer, should be weak - YES?
Second question: An NSDictionary in NSDictionary_B is passed to an object but it can change some of the values in that NSDictionary:
track.playbackType = [NSDictionary_B objectForKey:#"Random"];
[track.playbackType objectForKey:#"maxRange"] = 20;
So should I be making a copy of the NSDictionary here because it's values will be changed or am I completely misunderstanding this whole reference passing thang?
You are getting mixed up in how pointers work.
For the first question, "track.instrument" is just a pointer. So it will start as "pointing to nil".
this:
track.instrument = [NSDictionary_A objectForKey:#"Blue"];
means, "stop pointing to nil and point to that object"
If you can ensure your dictionary will persist for the entire app then it doesnt matter, whatever is at #blue key will never get dealocated. But for the sake of having the correct code, it should be weak.
Edit: Had read the second question incorrectly.
Second question:
about this:
track.playbackType = [NSDictionary_B objectForKey:#"Random"];
first your pointer points to the NSDictionary from the dictionary.
[track.playbackType objectForKey:#"maxRange"] = 20;
Since it is a NSDictionary this is not valid. You cannot change NSDictionaries because they are immutable, it SHOULD be NSMutableDictionary.
HOWEVER if you are not interested in putting back the modified version into the original dictionary then you can copy it but as a NSMutableDictionary first, and then change it.
NSMutableDictionary *mutableDict = [[NSDictionary_B objectForKey:#"Random"] mutableCopy];
track.playbackType = mutableDict; //Note how track.playbackType has to be NSMutableDictionary aswell.
VERY IMPORTANT: Since you are creating a "new" dictionary. track.playbackType has to be strong, or it will simply get instantly dealocated after the function ends and mutableDict gets out of scope.
References are just pointers, setting one to nil will have no effect except in the following case: It is the last strong reference and other weak references still exist. In that case all the weak references will become nil. Strong properties will set the old value to nil, in effect sending a release call but this affects the REFERENCE, not the CONTENT of the reference.
As for the second question, it is quite confusing and I need more info about playbackType. You say it is an NSDictionary but NSDictionary doesn't have the property maxRange so it must be a type that you defined. You can't change the values of an NSDictionary either because it is immutable.
But here is a generic answer: If you pass a pointer to a mutable object as strong (or weak even) you will be able to change the content of the original. If you pass a pointer to a mutable object as a copy you will get a new object that doesn't affect the original.

NSCopying and copyWithZone: - should they return [self retain] or something else?

I'm having a hard time understanding copyWithZone.
I know it's supposed to return a copy, but if I add an object to a dictionary, it adds a 'copyWithZone' object to the dictionary. If I make an actual copy (that is, a new object), then the object added to the dictionary will not be the same object. However, if I only increase the retain count, then it's not technically a copy.
Should I just retain self, or make an actual copy?
copyWithZone: is supposed to return an immutable object (if there are immutable and mutable versions of the class). If the original is immutable, simply retaining & returning the original would be safe, since none of the owners could mutate the object. Otherwise (i.e. original is mutable or the immutable/mutable dichotomy doesn't apply), return a copy.
As for NSDictionary and NSMutableDictionary, generally only the keys are copied (items are only copied if you explicitly say to with -initWithDictionary:copyItems:), which is necessary as the internal data structure of the dictionary depends on the key values. If you were to mutate a key being used by a dictionary, it would corrupt the dictionary's data structure and you would no longer be able to retrieve the item for that key, or worse.

Can I change an NSDictionary's key?

I have an NSDictionary object that is populated by instances of NSMutableString for its keys and objects. I have been able to change the key by changing the original NSMutableString with the setString: method. They key however remains the same regardless of the contents of the string used to set the key initially.
My question is, is the key protected from being changed meaning it will always be the same unless I remove it and add another to the dictionary?
Thanks.
The keys are -copy'd when the items are set, so you can't changing it afterwards is useless.
Methods that add entries to dictionaries—whether as part of initialization (for all dictionaries) or during modification (for mutable dictionaries)—copy each key argument (keys must conform to the NSCopying protocol) and add the copies to the dictionary. Each corresponding value object receives a retain message to ensure that it won’t be deallocated before the dictionary is through with it.
You could use CFDictionary with kCFTypeDictionaryKeyCallBacks, or just replace the item:
id value = [dictionary objectWithKey:oldKey];
[dictionary setObject:value withKey:newKey];
[dictionary removeObjectForKey:oldKey];
Try using NSMutableDictionary, instead.
You can create a copy of the dictionary, filtering the keys as you go. I do this for converting between camel-case and underscores when populating objects from JSON using KVC. See my refactoring library, es_ios_utils, for source. ESNSCategories.h provides:
#interface NSMutableDictionary(ESUtils)
//Changes keys using keyFilter. If keyFilter generates duplicate non-unique keys, objects will be overwritten.
-(void)addEntriesFromDictionary:(NSDictionary*)d withKeyFilter:(NSString*(^)(NSString*))keyFilter;
...
So to make all keys upper case, you could do something like:
NSMutableDictionary *md = [NSMutableDictionary dictionaryWithCapacity:oldDictionary.count];
[md addEntriesFromDictionary:oldDictionary
withKeyFilter:^NSString*(NSString *key) {
return key.uppercaseString;
}];

NSString retain copy question

I've seen a few posts on here about the issue of using retain or copy for strings. I still can't quite get my head around the difference or the importance.
In my case at the moment I have a class with a whole load of nsstrings to hold strings.
I want this class to only be instantiated once and I want its nsstring variables to change depending on the index clicked in a table view.
Would I be correct in saying that if I chose to use retain that my nsstrings would be overwritten each time I set their value on my tableview click and that if I chose copy I would somehow have 2 instances of each string....?
I'm sorry ..... I totally don't get it
This is a question about copying mutable objects vs. immutable ones. Since NSString objects are immutable (you cannot change their contents), they implement -copy like this:
- (id) copyWithZone: (NSZone *) zone
{
return [self retain];
}
If you think about it, there's no reason to duplicate an immutable object because that's a waste of memory. On the other hand, NSMutableString objects can see their contents change during their lifetime, so if you request a copy of an NSMutableString, you will get a real copy, a different object.
If your strings are not NSMutableStrings, it does not matter whether you retain or copy them. However, choosing the right method is important if you later refactor your code to use NSMutableStrings. A common logic should answer the following question for you: if I get an object whose contents may change outside, which value do I need? More often than not you will want to make a copy.