Object keys with NSMutableDictionary (Objective-C) - objective-c

I want to store a bunch of key value pairs, with the key being my own object (ObjectA) that inherits from NSObject, and the value being an int.
I am trying to use an NSMutableDictionary. I understand that you can only store object types in the dictionary, so I have the following:
id value = [NSNumber numberWithInt:my_number];
[[self dictionary] setObject:value forKey:myObjectA];
Now that gives me an error, saying
-[ObjectA copyWithZone:]: unrecognized selector sent to instance
which is fine, I understand that object keys need to implement the NSCopying protocol. However I then read that you can do this by wrapping your objects using NSValue.
Can someone please explain how I would wrap my objects, and how I can then find the value by the key? Am I still able to use dictionary objectForKey:myObjectA or do I have to wrap myObjectA with an NSValue object while I'm searching as well? Or should I be implementing NSCopying on my custom class, or using a string key instead?
I am looking for this simplest and easiest way to use a dictionary, if I have to I'll implement a string key and use setValue:forKey: instead but I'd rather use the object key if I can.

Dictionary keys are always copied. So you simply need to implement the NSCopying protocol for your class, which is just the copyWithZone: method.
Additionally you should implement the isEqual: method for your class.
Edit: How to implement your copyWithZone: depends on a number of factors (main factor: deep vs. shallow copy). See Apple's Implementing Object Copy guide and this SO answer.

You could turn an id into an NSValue with:
NSValue* value = [NSValue valueWithNonretainedObject:object];
...
id object_ = [value nonretainedObjectValue];
but you need to manage the ownership outside of the dictionary. This is going to be a mess. It's better to adopt NSCopying.
There is also a 4th option: use a CFDictionary, which allows the object only can be CFRetain/CFReleased, not copied.
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(
kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks
);
...
CFDictionarySetValue(dict, myObjectA, value);
...
CFRelease(dict);
And if you're programming for Mac or iOS 6 and above, try NSMapTable.
NSMapTable* dict = [[NSMapTable mapTableWithStrongToStrongObjects] retain];
...
[dict setObject:#"?" forKey:foo];
...
[dict release];

In iOS 6 you can use NSMapTable (https://developer.apple.com/library/ios/#documentation/Cocoa/Reference/NSMapTable_class/Reference/NSMapTable.html), which allows you to chose weak/strong attributes for the keys and objects.

You don't need to wrap your object using NSValue. What you have will work except you're missing a piece. For myObjectA's class you need to adopt the NSCopying protocol (see the docs for what to add). Once that's added the code you posted above should work correctly.
You might want to consider using strings though over your own object for the key. The key is required to be a string if key-value coding is going to be used to access it at all. So using a string will make life easier if you can take advantage of key-value coding anywhere you're using the dictionary.

Related

Picking an item of NSArray using key value coding

If you read listing 4 in the Animation section Apple's Core Animation guide, it seems to use KVC and the key path "filters.pulseFilter.inputIntensity" to pick out an object called "pulseFilter" out of an NSArray. "pulseFilter" is actually a CIFilter named "pulseFilter" by calling the method setName.
Now, I don't see the method setName defined anywhere. I also don't believe you can select a specific item out of an NSArray by using a key. Can someone explain how this works?
It does not necessarily have to go through standard valueForKey: or valueForKeyPath: of the NSArray.
Objects are free to provide own valueForKeyPath: method and handle KVC its own way and this is what probably the class of this selectionLayer object does.
To retrieve an object from NSArray using a property name + property value.
Step 1: get your stored object compliant to KVC (NSKeyValueCoding protocol) by implementing valueForKey: in your custom class.
Step 2: filter your NSArray using NSPredicate (filteredArrayUsingPredicate on your NSArray)
Do you need more detail ?

Objective-C HashMap equivalent

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

NSMutableDictionary. Using an object to store an integer value

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. :)

What is the difference between valueforKey:, objectForKey:, and valueForKeyPath:?

I have 2 questions:
What is the difference between valueForKey: and objectForKey:? Is it that one is for NSDictionarys (objectForKey:) and for others it is valueforKey:, or is it the reverse?
Also what is the difference between valueForKey: and valueForKeyPath:? Has it got something to do with Core Data?
Please help.
valueForKey: is part of the NSKeyValueCoding protocol and is therefore part of the key-value coding framework, which allows you to access class properties by name at runtime. That's how NIBs are loaded, for example — the names of properties associated with connections are loaded and then the values are set directly by name. This contrasts with the way that visual interface design tools often work in other languages, generating lots of hidden statically compiled code.
objectForKey: is defined on dictionaries only, and looks up an object by its key. An NSDictionary is a class that stores connections between values and keys.
So, valueForKey: could be used on an NSDictionary to return meta information about the dictionary, such as the count of objects inside it, the list of all keys, etc. objectForKey: would be used actually to look into the dictionary.
At runtime, the difference is that objectForKey: is a method with a completely opaque implementation. valueForKey: explicitly relies on subsequently calling named getters and setters. The reason for the latter is that you can extend key-value coding to key-value observing, where you ask to be informed every time a particular property on a particular object changes. At runtime that's achieved with a method swizzle, where the original setter is replaced by a new one that calls the previous setter and then sends out the required messages. Because all messages are dispatched dynamically, that's just achieved by modifying tables within the object instance.
So any object that is key-value coding compliant (which just means declaring and implementing your properties in the proper way, which the new-ish #property/#synthesize syntax does automatically) can be observed without the object itself having to implement any code.
There's further Apple stuff that uses key-value coding to achieve various things, including CoreData, but it's not specifically to enable any one other technology.
valueForKeyPath: is like valueForKey: except that it can traverse several objects. So you can have a root object with a bunch of properties, each of those properties is another object with another bunch of properties, etc, and using a key path you can retrieve a value way out at the leaf of that data structure rather than having to iterate through object after object for yourself.
In summary, valueForKey: and valueForKeyPath: provide information about object instances and interact with the dynamic nature of the Objective-C runtime. objectForKey: is a dictionary specific method that does dictionary tasks.
Additions:
An example, coded as I type and assuming that NSDictionary is key-value coding compliant:
NSDictionary *someDictionary;
// create someDictionary, populate it, for example (note: we assume photoOfKeys.jpg
// definitely exists, not a good idea for production code — if it doesn't we'll get
// a nil there and anything after it won't be added to the dictionary as it'll appear
// that we terminated the list):
someDictionary = #{ #"favouriteGarment": #"hat",
#"#allKeys" : [NSImage imageNamed:NSImageNameDotMac],
#(2) : NSArray.new };
NSObject *allKeys;
// we make no assumptions about which type #allKeys will be, but are going to assume
// we can NSLog it, so it needs to be a descendant of NSObject rather than 'id' so as
// to definitely respond to the 'description' message — actually this is just compile
// time semantics, but if someone else reads this code it'll make it obvious to them
// what we were thinking...
// some code to get all of the keys stored in the dictionary and print them out;
// should print an array containing the strings 'favouriteGarment', '#allKeys' and
// the number 2
allKeys = [someDictionary valueForKey:#"#allKeys"];
NSLog(#"%#", allKeys);
// some code to get the object named '#allKeys' from the dictionary; will print
// a description of the image created by loading photoOfKeys.jpg, above
allKeys = [someDictionary objectForKey:#"#allKeys"];
NSLog(#"%#", allKeys);
// `objectForKey is analogous to `objectForKeyedSubscript:`, aka
allKeys = someDictionary[#"#allKeys"];
allKeys is a property of NSDictionary as described here. I've also added a mapping from the NSString allKeys to a photograph of some keys. Whether I use the key-value coding valueForKey: methods or the NSDictionary objectForKey: lookup method dictates whether I read the property of the object instance or whether I send the object instance a message asking it to do its unique job.
objectForKey: is a method on NSDictionary for accessing the object associated with a key. valueForKey: is a method on NSObject for accessing any value associated with any object, through the name of a accessor method, property, and/or instance variable.
valueForKeyPath: can be seen as a shorthand for several calls to valueForKey:. You can think of it as sort of a xpath, if you will.
These two statements will result in the same output:
// Using nested valueForKey:
NSLog(#"%#", [[myObject valueForKey:#"foo"] valueForKey:#"bar"]);
// Can be done with a single valueForKeyPath;
NSLog(#"%#", [myObject valueForKeyPath:#"foo.bar"]);
valueForKey:and valueForKeyPath: are part of KVC (Key Value Coding). Introduction and in-depth documentation can be found here: http://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/KeyValueCoding/
valueForKey: and valueAtKeyPath: are methods defined in the NSKeyValueCoding informal protocol, and default implementations for both are provided by the root class NSObject.
objectForKey: is a method on NSDictionary.
valueForKey: takes a key to a property, while valueAtKeyPath: takes a so-called keypath. A keypath is a period-delimeted path to a specific property, like #"relationship.property".

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;