Does valueForKey: NSMutableDictionary use copy for NSStrings? - cocoa

I have an NSMutableDictionary with a dozen keys, each key refers to an NSString value. If I call [myDictionary valueForKey:#"abc"] I will get an NSString object.
Is this NSString copied?
As there is no #property declaration, what happens with this retrieval of the string? I assume I get a copy. Is it any different with a NSMutableString?
My purpose is that I have an array of NSDictionaries. I call valueForKey on the array to get all the #"abc" keys from the collection of dictionaries. Thus I now have an array of NSStrings.
I need to pass that array to an NSOperation on a background thread. Thus, the NSStrings need to be copies so that modifying the dictionaries later will not cause the ones passed to the NSOperation to get changed.

From the documentation of -[NSDictionary valueForKey:]:
If key does not start with “#”, invokes objectForKey:. If key does start with “#”, strips the “#” and invokes [super valueForKey:] with the rest of the key.
From the documentation of -[NSMutableDictionary setObject:forKey:]:
If aKey already exists in the dictionary, anObject takes its place.
valueForKey: does objectForKey: and does not copy the object. setObject:forKey: replaces the object, it does not modify the object. If you do
[mutableDictionary setObject:#"klm" forKey:#"abc"];
a = [mutableDictionary valueForKey:#"abc"];
[mutableDictionary setObject:#"xyz" forKey:#"abc"];
now a is the old value #"klm".

NSDictionary keys are copied when set. In the code dictionary[key] = value the key object is copied, and the dictionary retains the copy. The value object is not copied; the caller and the dictionary share a reference to the same object.
When retrieving keys or object, there is no copying. dictionary.allKeys returns an array of the key objects in the dictionary, not copies. Similarly, dictionary[key] returns a reference to the value for key in the dictionary.
And just so you know, the #property (copy) only applies when setting the value. ObjC getters never copy objects, unless it is specifically documented that way or you write your own so it does that.
Similarly, and to get to your question, key-value method use the same underlaying rules for that property. So -setValue:forKey:, -setValue:forKeyPath:, -valueForKey:, -valueForKeyPath: etc. will get or set the proper value as if you accessed the property directly. In other words, if the value is copied when the property is set, -setValue:forKeyPath: will copy the value. If an accessor does not copy the value, then -valueForKey: will not copy the value.

Related

Archived NSData, isEqualToData: and Empty Strings

I have a custom object containing several NSString objects, some ints and a few bools. I am using NSKeyedArchiver to archive a copy of the object into an NSData object.
The user than makes changes to the object variables, which are connected to an IB interface.
After the changes are made, the new version of the object is archived into a second NSData object.
These two objects are compared using
[myNSData1 isEqualToData: myNSData2];
In most cases it works perfectly well, but there is one very troubling situation:
Let's say the object had a variable initialized as follows:
NSString *myString = #"";
After the object was archived into myNSData1, we called the following:
myString = [myNSTextField stringValue];
Logging myString to the console reveals that the value of myString is still
#""
and thus has not changed value.
We now archive the object into myNSData2.
Upon executing the comparison statement above, however, it now returns FALSE. It ONLY returns FALSE if the original assignment of #"" is replaced with the #"" contained in the textfield using stringValue.
Why is this?
Have a look at the types of those strings (NSLog([myString className]) should work), because NSString is a class cluster. I'm guessing that you'll find that and one of those strings is an NSCFString, and the other is an NSCFConstantString. The archiver encodes type information, so if the types are different, the NSData will also be different.
I wouldn't rely on the NSData objects being identical. If you want to compare the two, you'd be better off unarchiving them and using isEqual:. That way, you'd have full control.

Cocoa - NSDictionary objectForKey - memory management clarification

[I have read the Cocoa memory management rules, but still want to be certain, and would like to know if this is good form.]
My class has a mutable dictionary ivar:
NSMutableDictionary *m_Dict;
...
m_Dict = [NSMutableDictionary dictionaryWithCapacity:10];
[m_Dict retain];
At some point I'll add a mutable array to the dictionary:
NSMutableArray *array = [NSMutableArray arrayWithCapacity:100];
[m_Dict setObject:array forKey: #"myArray"];
At this point, I believe that the array object has been retained by the dictionary. Therefore I am not retaining the object prior to adding it to the dictionary. Is this correct?
Later, I will access the array, for read purposes:
NSMutableArray *array = [m_Dict objectForKey: #"myArray"];
Q1. What is being returned by objectForKey? Is it a pointer to the object being held in the dictionary? Or a pointer to a COPY of the object? (I am presuming simply a pointer to the existing object is being returned.)
Q2. What has happened here, memory management wise? I am presuming that 'array' points to an object that is still retained (by the owning dictionary object) and that I do not have to retain the object to work with it. Correct?
Thanks.
Q1) The same pointer to the Objective-C array that you added before is returned. No copy is made.
Q2) Correct. All items in the array are owned by the array. If something owns something else it keeps a retain count on it.
When you remove the object from the array the retain count is reduced and the object is (maybe) deallocated.

Avoiding sorting in NSDictionary

I was using NSDictionary and I observed that the objects in the dictionary are sorted automatically with respect to the keys, so my question is how to avoid this sorting, any flag available to set it off, so I get the object in same order I entered.
I know you may be thinking what difference it makes in dictionary since we retrieve the value with respect to key, but I am first getting allKeys from which I receive Array of keys, this order is what I need it to be in the order of how I entered just as in NSArray.
I guess that there will not be a method to control the sorting in NSDictionary, because 'NSDictionary' is a hash table which uses hash function to chose the place to store the values(for speedy accessing).
To customize dictionary you can use CFDictionaryRef there you can write your custom callback. You can write your own CFDictionaryHashCallBack function to compute the place for storing. CFDictionary is "toll-free bridged" with NSDictionary. RegardsDevara Gudda

Cocoa's NSDictionary: why are keys copied?

All objects used as keys in NS(Mutable)Dictionaries must support the NSCopying protocol, and those objects are copied when they're used in the dictionary.
I frequently want to use heavier weight objects as keys, simply to map one object to another. What I really mean when I do that is effectively:
[dictionary setObject:someObject forKey:[NSValue valueWithPointer:keyObject]];
("When I come back and hand you this same key object instance again, get me that same value out.")
...which is exactly what I end up doing to get around this design sometimes. (Yes, I know about NSMapTable in desktop Cocoa; but e.g. iPhone doesn't support this.)
But what I don't really get is why copying the key is necessary or desirable in the first place. What does it buy the implementation or caller?
The copy ensures that the values used as keys don't change "underhand" while being used as keys. Consider the example of a mutable string:
NSMutableString* key = ...
NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
[dict setObject: ... forKey: key];
Let's assume that the dictionary did not copy the key, but instead just retained it. If now, at some later point, the original string is modified, then it is very likely that you are not going to find your stored value in the dictionary again even if you use the very same key object (i.e., the one key points to in the example above).
In order to protect yourself against such a mistake, the dictionary copies all keys.
Note, by the way, that it is simple enough to define -copyWithZone: as just doing return [self retain]. This is allowed and good code if your object is immutable, and the NSCopying contract is specifically designed such that the object returned has to be (sorta, kinda) immutable:
Implement NSCopying by retaining the original instead of creating a new copy when the class and its contents are immutable.
(from NSCopying Reference)
and
The copy returned is immutable if the consideration “immutable vs. mutable” applies to the receiving object; otherwise the exact nature of the copy is determined by the class.
(from -copyWithZone: Reference)
Even if your objects are not immutable, you might get away with that implementation if you only ever use identity-based equality/hash implementations, i.e., implementations which are not affected in any way by the object's internal state.
If you want to store pointers as keys then you'll need to wrap them in a NSValue object with +valueWithPointer:.
Since iOS 6 if you want to use pointers as keys, you can use the NSMapTable object, see http://nshipster.com/nshashtable-and-nsmaptable/
You can specify whether keys and/or values are stongly or weakly held:
NSMapTable *mapTable = [NSMapTable mapTableWithKeyOptions:NSMapTableStrongMemory
valueOptions:NSMapTableWeakMemory];
Another option that could be appropriate sometimes is to use NSCache, which holds keys strongly and is actually thread-safe.

Cocoa String Question

I have a NSString and I want to write its value to a NSMutableString. Is this valid:
NSString *temp = [NSString stringWithString:#"test"];
NSMutableString *mutable = temp;
I ask because although this seems doable, I would think that this would assign both temp and mutable to the same address space. I have this question a lot when passing values into a method or returning values from a method. Sometimes I see other people do this or create the mutable string with stringWithString or initWithString. Thanks
You can use the mutableCopy method, which creates a mutable copy of the receiver and applies to any class which adopts the NSMutableCopying protocol (of which NSString is one of them):
NSString *temp = [NSString stringWithString:#"test"];
NSMutableString *mutable = [temp mutableCopy];
This will create a mutable copy of the string, as a new string instance. In this case it doesn't apply, as temp is an autoreleased string, but you would otherwise need to release the old string that you have made a copy of, if you no longer need it.
Since mutableCopy contains "copy", then you need to memory-manage the new string (you take ownership of it according to the Apple Object Ownership Policy).
The method that you have used simply assigns mutable as a pointer to the previously instantiated NSString.
One thing that seems to confuse a lot of people with Cocoa is the difference between variables and objects.
You have to keep in mind that when you declare a variable like NSMutableString *mutable, you are not creating a string. This is just a pointer variable of type NSMutableString*. Structurally, it's the same as any other pointer — and, as the term "pointer" implies, it just points to something that actually lives elsewhere. Because of this, you technically can assign it to point to any object. But you're not turning that object into a mutable string — you're just lying about what kind of object the variable points to. Once you try to send the object a message that only NSMutableString can respond to, the jig is up!
As Perspx said, if you have a string that you want to mutate, you can use the mutableCopy method to get — you guessed it — a mutable copy. There are also a lot of NSString methods that don't mutate the string, but let you get a new string with certain changes made (for example, stringByAppendingString:). You can go a pretty long way with those.

Resources