Objective C: updating values for key for an NSMutableDictionary? - objective-c

I have an NSMutableDictionary with keys and values set up initially.
Later I need to update the values for certain keys, and I thought I would just need to do
[mutableDict setValue:val forKey:key], but this throws an exception saying:
[__NSCFDictionary setObject:forKey:]: mutating method sent to immutable object
What am I doing wrong?
(I have another mutable dictionary inside each key of the mutable dictionary, but I don't think that's why)

I'll copy an paste my comment just incase you want to keep your 100% accept
Paul.s
For some reason you are actually working with an NSDictionary not an NSMutableDictionary. Hard to say why without more code, maybe you are using a copy obtained by calling copy or you have loaded the dictionary from a plist/userDefaults.. Can we see some more code?
Dennis
#Paul.s Yes, I did copy an NSMutableDictionary. Would that be why? If it is, then what else should I use to copy?
Paul.s
Use mutableCopy instead of copy to get a mutable dictionary
Dennis
#Paul.s oh.. didn't know about that. I'm new to Objective-C and I tried to search for what I am doing wrong but I missed it :( Thank you!!

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.

Re-initialize NSMutableArray as NSMutableArray

I was having a problem with my app throwing an exception when calling removeObjectAtIndex on an NSMutableArray, saying that myLocationsArray was declared immutable. All other manipulation on that array was fine, it was most definitely declared correctly etc etc but somewhere in my app it was getting set as immutable. After investigating for a while trying to find where it was getting set immutable, I decided screw it and just redeclared the variable as such:
myLocationsArray = [[NSMutableArray alloc] initWithArray:[defaults
objectForKey:MYLOCATIONSARRAY_KEY]];
right before the removeObjectAtIndex call.
However I know this has got to be badwrong, I'm calling alloc/init twice on the same variable. However it's the only thing that has worked. Is there any way to remind this variable that it is an NSMutableArray without introducing memory leaks like I am?
NSUserDefaults returns immutable copy of your array. It doesn't matter whether you put NSArray or NSMutableArray in it, it always give you immutable copy back.
So, do this to get a mutable copy that you can work with
myLocationsArray = [[NSMutableArray alloc] initWithArray:[[[defaults objectForKey:MYLOCATIONSARRAY_KEY] mutableCopy] autorelease]];
or just this
myLocationsArray = [[defaults objectForKey:MYLOCATIONSARRAY_KEY] mutableCopy];
I would suggest to set a breakpoint on the line where your program is throwing an exception (the one containing removeObjectAtIndex) and inspect with the debugger the real type of the array. If you go with you mouse over the array name, a popup menu will display giving you all the information you need about the pointed object.
What I expect is that you find out this way that the object is an NSArray (vs. NSMutableArray) and then trace back to the point where you initialized it in the first place.
It looks like you're working with NSUserDefaults. All objects you get out of NSUserDefaults are always immutable, regardless of what you stored into it. NSUserDefaults doesn't keep a reference to the specific object you set into it, it keeps the data. It's effectively making a copy. When you get something out of NSUserDefaults, it makes a new (immutable) object from the data it has stored and gives that to you.
Unsurprisingly, you can't change what's stored in NSUserDefaults by mutating what you (think you) stored in it. You can only change what's stored by replacing what you previously stored by storing something anew.
The declaration should not matter; your error is a run-time error. It sounds like your myLocationsArray variable has been assigned an immutable array (NSArray) though whether it is being re-assigned somewhere or was always immutable is impossible to say from your code fragment.

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.

addEntriesFromDictionary: a true copy method?

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.

Object pointer value as key into dictionary

I want to use the object's reference value as a key into a dictionary, as opposed to a copy of value of the object. So, I essentially want to store an object associated with a particular instance of another object in a dictionary and retrieve that value later.
Is this possible? Is it completely against the idea of NSDictionary? I can tell that I am probably approaching this the wrong way because the dictionary wants me to implement NSCopying on the object itself, which doesn't really make sense in terms of what I'm doing. I can see that what I should really be doing is wrapping the pointer value, but that seems a little mad.
Advice would be appreciated.
I think you can use [NSValue valueWithPointer:object].
NSMutableDictionary has been designed to only deal with Objective-C object instances. For example, when you call setObject:forKey: method calls copyWithZone: on the key and retain on the value.
If you want to have a dictionary structure and to be able to deal with arbitrary key and value, then you can go with CFMutableDictionary. You can describe precisely what is done with key and values; it is flexible enough to deal with arbitrary pointer or event char * strings.
This did the trick for me
aDictionary[#((intptr_t)object)] = ...;
You can use the address in memory of myObejct as a key in myDictionary
NSString *myObject_addressInMemory = [NSString stringWithFormat:#"%p", myObject];
myDictionary[myObject_addressInMemory] = someValue;