I noticed that for an object to be a key for an NSDictionary it must conform to NSCopying.
Why is this so? I would understand that all keys must implement hash, but why NSCopying?
Because the keys are copied. You wouldn't want a key to be stored as a reference to the very same object you started with, would you? It would be terrible if d[myThing] = myValue retained a reference to a possibly mutable instance myThing. That would mean that the dictionary could get mutated behind its own back.
NSDictionary guaranties that if you store a value with some key x this key is fixed and you can retrieve this value with the equivalent key y (y.isEqual(x) == YES). There are only two possibilities to do so:
Copy keys to prevent them from changing.
Demand that keys are immutable.
Apple decided that for most cases coping keys is better.
In case you need a dictionary were keys are not copied (for example keys do not implement NSCopying or coping is too expensive) you can use NSMapTable.
For example you can use
[NSMapTable strongToStrongObjectsMapTable]
to store keys and values as a strong references.
Related
I understand that a class must implement NSCopying in order to be a key of an NSDictionary, but is implementing isEqual: and hash also necessary or advisable?
If yes, why?
Yes.
Why?
Consider accessing an element of a dictionary, how does NSDictionary find the object associated with a key? By comparing the key value you provide with the keys in the dictionary.
It is a rule when you implement isEqual: that you also implement hash, objects which compare equal must have the same hash. Consider further how the dictionary may organise the storage of the key/value pairs, it uses a hash-based storage structure.
HTH
Addendum
Seeing what I guess is a related question you've also asked I will qualify the the above "Yes":
If a class inherits isEqual: and hash methods which appropriately define equality for itself it need not override the methods with its own versions. In all probability this will not be true if the class directly inherits from NSObject.
You can find it in Documentation:
https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/index.html
A key-value pair within a dictionary is called an entry. Each entry
consists of one object that represents the key and a second object
that is that key’s value. Within a dictionary, the keys are unique.
That is, no two keys in a single dictionary are equal (as determined
by isEqual:). In general, a key can be any object (provided that it
conforms to the NSCopying protocol—see below), but note that when
using key-value coding the key must be a string (see Key-Value Coding
Fundamentals). Neither a key nor a value can be nil; if you need to
represent a null value in a dictionary, you should use NSNull
I'm dealing with an NSMutableDictionary with NSManagedObject keys. Some of the data in my keys are going to be mutated, but the NSMutableDictionary class reference states that the keys are copied when using setObject! And when I want to access values using keys, it's going to compare the key I give it to the keys in the key list.
Sorry if this is a dumb question, but I can't find on the NSManagedObject class reference what isEqual does. Does it compare every value, compare some unique identifiers, compare the memory addresses, or something else?
I've seen some unsure answers here: Should I use == or [NSManagedObject isEqual:] to compare managed objects in the same context?, one of them saying that it compares the hash methods. Hash codes are not necessarily unique identifiers of objects, right?
I see others have answered in the comments - but for the "officiality" of it - here is the proper detailed answer.
Since NSManagedObjcet class does not conform to <NSCoding> protocol - it cannot serve as a key in NSDictionary or NSMutableDictionary.
Rather - you would want to use the objectID property of your managedObject as key in a dictionary. objectID is an NSString, which of course conforms to NSCoding.
Using objectID has another benefit. If you have more than one NSManagedObjectContext, The same entity will have a different instance of the NSManagedOBject for each NSManagedObjectContext. The only way to know they are one - is via their objectID property - which will be isEqual.
I am trying to convert a piece of Java code which uses a HashMap that contains as a key an object and a value as an object.
private static HashMap<Class<? extends Component>, ComponentType> componentTypes = new HashMap<Class<? extends Component>, ComponentType>();
I've been reading on how to do this with Obj-C but I have not been successful, most people suggest using a NSDictionary, the problem is that they keys need to be strings and I need them as objects. The other option was NSMapTable, however it is not available on iOS. Would someone be able to assist on how I can convert this into an obj-c equivalent?
thanks,
The keys for an NSDictionary do not need to be strings. They can be any object that implements NSCopying. If the object is a custom object, however, it needs to produce sane responses to the -hash and -isEqual: messages, but this is the same as using an object in a Java collection so it shouldn't be much of a challenge.
An NSMutableDictionary (assuming that you also need to set values in the dictionary after its initialization) works in two ways:
As a traditional dictionary/hashmap in which you set values like this:
[myDictionary setObject: theValue forKey: anyObject];
As an object with KVC-compliant properties that happen to be defined dynamically:
[myDictionary setValue: theValue forKey: aString];
If the key is an NSString, then the two are interchangeable, with the exception that you can't set an object to nil with setObject:forKey:, but you can pass nil to setValue:forKey:.
You want to use an NSDictionary. You say that
they keys need to be strings and I need them as objects
The keys to an NSDictionary don't need to be strings -- they can be any object that conforms to the NSCopying protocol.
From NSDictionary reference
A key-value pair within a dictionary is called an entry. Each entry consists of one object that represents the key and a second object that is that key’s value. Within a dictionary, the keys are unique. That is, no two keys in a single dictionary are equal (as determined by isEqual:). In general, a key can be any object (provided that it conforms to the NSCopying protocol—see below), but note that when using key-value coding the key must be a string (see “Key-Value Coding Fundamentals”). Neither a key nor a value can be nil; if you need to represent a null value in a dictionary, you should use NSNull.
So any object that meets the NSCopying protocol can be used as a key. The string restriction is only for Key-Value Coding used for Cocoa bindings
I'm inferring that you are using a key that is does not conform to the NSCopying Protocol. In that case try using the Core Foundation equivalent of NSDictionary: CFDictionary.
http://developer.apple.com/library/ios/#documentation/CoreFoundation/Reference/CFDictionaryRef/Reference/reference.html
Just make sure that when you are using CFDictionary that all of your objects are going to be retained in memory for the duration of the object. Since CFDictionary has to be set with weak references (at least in my experience) just be careful that you don't accidentally dealloc one of your objects whiles it's still in the CFDictionary.
While CFDictionary is “toll-free bridged” with its Cocoa Foundation counterpart, NSDictionary, I find that there are still problems with this. I've tried to add objects that were not part of the NSCopying protocol to my toll-free bridged NSDictionary and it came up with an error at run time.
CFDictionary docs: http://developer.apple.com/library/ios/#documentation/CoreFoundation/Reference/CFDictionaryRef/Reference/reference.html
If you need mutability, use CFMutableDictionary instead like so:
cfd = CFDictionaryCreateMutable(NULL, 0, NULL, NULL);
CFDictionaryAddValue(cfd, key, value);
CFRelease(cfd);
CFMutableDictionary docs: http://developer.apple.com/library/ios/#documentation/CoreFoundation/Reference/CFMutableDictionaryRef/Reference/reference.html#//apple_ref/doc/uid/20001497
I'd like to add instances of my custom class to a NSMutableDictionary so that each instance has a corresponding integer value? I later want to be able to retrieve the integer value using the class instance as the key.
NS*Dictionary requires objects for both keys and values. If you want to shove an integer into a collection instance (dictionary, array, set, etc.) then you must "box" it first using NSNumber; [NSNumber numberWithInt:x];.
The keys to an NSMutableDictionary generally need to be copyable and must have a stable hash and stable isEqual: behavior (as per the documentation). Thus, your instances of your custom class must fulfill the NSCopying protocol and must properly support hash and isEqual:. hash and isEqual: may likely "just work" if pointer equality is good enough. Copying can be tricky.
In general, though, it is rare to have a map between instances and integral values. Why not just add an #property to your class?
i.e.
#property int magicNumber;
Far more straightforward and a heck of a lot more efficient, too.
Note, also, that if you need to associate something with an existing instance where you can't modify the class, you should use objc_setAssociatedObject() and objc_getAssociatedObject(). They don't require that the instance be NSCopyingable and are relatively efficient, too (though not as efficient as an #property).
You should be able to simply use [myMutableDict setObject: [NSNumber numberWithInt: myInt] forKey: myClassInstance];
At least thats what i read out of the fact that setObject takes (id) as parametertypes. :)
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.