How to use an object as a key in Objective-C - iphone

I would like to use a custom object as a key in a hash-like structure. I've tried using NS[Mutable]Dictionary but in order for my object to be a key it has to implement the NSCopying protocol. NSDictionary is sending a copy message to all of it's keys as far as I've read. I don't want to implement the protocol (my object is quite complex) nor do I want it to be copied. What are my options? Do I have any?

NSDictionary is toll-free bridged with CFDictionaryRef, but they actually differ in behavior when adding objects. Specifically, NSDictionary's -setObject:forKey: will copy the key, but CFDictionaryRef's CFDictionarySetValue() will not copy the key. This means that if you want to use non-copyable keys, you can use CFDictionarySetValue() instead to add it to the dictionary.
CFDictionarySetValue((CFMutableDictionaryRef)myDict, myKey, myValue);
This will still retain the key, but it won't copy it. And you can use the normal NSDictionary methods for everything else.

Do you need the NSDictionary to retain the object? If not, you can turn it into an NSValue and use that as the key:
NSValue *value = [NSValue valueWithNonretainedObject:yourCustomObject];
[dictionary setObject:someObject forKey:value];
This can get a bit messy but is in alternative to implementing NSCopying.

You can roll your own dictionary. Not really that hard.
Another option is to use a surrogate object, containing a pointer to "the" object. The surrogate would implement the hash and either copy or reference the fields to be compared for isEqual. It could do a basic sanity check to assure the compared fields have not been changed when it's referenced.

You could just do this:
- (id)copyWithZone:(NSZone *)zone {
return [self retain];
}

Related

CoreData Object typing won't work

Can someone explain to me why this doesn't work:
CoreDataClass *classObject = (CoreDataClass *)[some method that returns a dictionary with exact KVC pairs that match CoreDataClass];
NSString *myString = classObject.stringProperty;
But this does:
CoreDataClass *classObject = (CoreDataClass *)[some method that returns a dictionary with exact KVC pairs that match CoreDataClass];
NSString *myString = [classObject valueForKey:#"stringProperty"];
EDIT:
What's the easiest way to cast the dictionary as my NSManagedObjectClass CoreDataClass so I can access properties directly?
It doesn't work since KVC compliance is not at all what defines classes or makes them castable - the class hierarchy exists for a reason, and just ensuring adherence to certain methods doesn't magically make something an instance of a completely different class. Keep in mind that the dot-accessor syntax is just sugar for a method send, so these two are equivalent:
classObject.stringProperty
[classObject stringProperty]
...and the latter obviously isn't valid for instances of NSDictionary (i.e. [[NSDictionary class] instancesRespondToSelector:#selector(stringProperty)] is NO).
Your latter example works because of the very premise of your question: if something is KVC-compliant for the key stringProperty, and you ask it for a value for that key, then obviously you get something back. Furthermore, both NSDictionary and CoreDataClass respond to the selector -valueForKey:, so the message send actually works at runtime.
The best way to get the two across isn't a "cast" at all - it's a complete conversion, at the property level, of the data involved. You might consider creating a custom -initWith... method on CoreDataClass that lets you instantiate its properties from a dictionary, or finding a way to get your method to return an actual instance of CoreDataClass instead of an NSDictionary.
Note that this solution may differ from the "easiest" way to get the data across, which is effectively to keep doing what you're doing and use -valueForKey: (though preferably without the cast, which is misleading).
Casting objects only appears to work (in the sense that you won't get type-checking errors) because it's a hint to the compiler, but it doesn't actually change anything about what the pointer points to, so you are still pointing to an NSDictionary. This is because, at the end of the day, you are essentially casting a pointer to a pointer, but telling Xcode that you are allowed to send a different set of selectors to it.
For NSManagedObjects, creation from a dictionary depends on a few things, but the recommended way is to make a class method on your custom class which will use NSEntityDescription and you NSManagedObjectContext, and sets the properties from the dictionary to the object:
+(CoreDataClass *) coreDataObjectWithDictionary:(NSDictionary *) spec {
CoreDataClass *myInstance = [NSEntityDescription insertNewObjectForEntityForName: #"CoreDataClass" inManagedObjectContext: [myMOCProvider sharedMOC];
myInstance.someProp = [spec valueForKey:#"someProp"];
}

Accessing array/set of objects with keys

I want to be able to add objects to an NSArray and access them with Keys. Currently the way im doing it is creating a seperate NSDictionary of key-value pairs where the value is an integer number representing the index in my NSArray. This seems like an extra step to me.
If my understanding of NSDictionary is correct, only 'values' can be stored: a pointer to an object cannot.
Surely there must be an equivalent NSDictionary type function that allows objects to be stored and accessed with a key? I have looked through the documentation, but cant seem to find any answers, unless im missing something obvious.
NSDictionary is to store key value pairs. if you are adding key value pair after you created the dictioanry, use NSMutableDictionary class . example,
[dictionaryObject setObject:#"" forKey:#"abc"];
You can store objects in NSDictionary and can be accessed via keys...
In short, no.
An array (NSArray) is an ordered collection of references to objects, so simply said, an ordered collection of objects.
As opposed to dictionaries, which are unordered and values are accessed by keys.
You understanding of collections is probably wrong, you don't store values, but pointers (references).
The extra step is necessary if you need to store the references in an array, but in this case, you should consider using a dictionary. An option is to use keys that take care of the order.
For example :
[myDictionary objectForKey:#"1"];
could be an equivalent of :
[myArray objectAtIndex:1];
Thats wrong, you can store objects in a NSDictionary. Look at the method dictionaryWithObjects:forKeys: or dictionaryWithObjectsAndKeys:
I have no experience in Cocoa but looking at the documentation it seems like NSDictionary (or at least NSMutableDictionary) should handle your request (without you using NSArray).
I think I understand your problem. My suggestion for you is to use NSMutableArray and macros, like:
NSMutableArray *array=[[NSMutableArray alloc]init];
#define SOME_MACRO 0
id someObject;
[array insertObject:someObject atIndex:SOME_MACRO];
id getterObject=[array objectAtIndex:SOME_MACRO];
Of course define the macros in the header file.

What classes can be used as a key in NSDictionary?

Can we use only NSString objects as key in an NSDictionary? How do we know which objects can be used and which cannot?
From the documentation:
In general, a key can be any object (provided that it conforms to the NSCopying protocol), but note that when using key-value coding the key must be a string (see “Key-Value Coding Fundamentals”).
So you can use anything copyable besides strings, but they'll be problematic with KVC. I just use strings for keys to keep things safe, consistent and simple.
You can use anything that conforms to NSCopying. That is, you can use id - type objects, as long as they conform to NSCoding protocol.
In instances where the key is NSString, then isEqualToString: is called for retrieval. Otherwise, isEqual: is called on the object to determine whether the key matches the requested key.
The key (and value for that matter) cannot be nil or NULL. They can, however, be [NSNull null].

Extracting the copyable identity of the non-copyable object

I need to use non-copyable objects as keys in the NSMutableDictionary which, by default, is not allowed. I understand the reasons for this not being allowed (retaining the key object, unlike the value object, is undesirable), but it seems like in my particular situation there could be a way around this.
The thing is that I'm only need to query the dictionary using the key's address, i.e. having the lookup predicate
if (providedKey == storedKey)
instead of
if ([providedKey isEqual:storedKey])
would be perfectly sufficient.
Is there a way of extracting the object's reference address (or other form of identity) as a copyable comparable object which I could use as a dictionary key instead of the object itself?
It seems that +[NSValue valueWithPointer:] might be what you want. It stores the pointer itself in an object that conforms to NSCopying, so that you can use it as a dictionary key. Retrieve the pointer using pointerValue.
You could use [NSString stringWithFormat:#"%p", someObject] as your key.
This will create a string with the object's address as a hex value.
You could use an nsnumber that represents the hash of the object.
you can drop down to the CoreFoundation APIs and define you own callbacks (among other things).
NSMutableDictionary is a CFMutableDictionary.
specifically, you want to create a CFMutableDictionary, and define your own CFDictionaryKeyCallBacks.

How to store object + string pairs without retaining the objects, and what kind of storage to use?

I am implementing a class that has to store arbitrary objects together with a string. i.e.
myUIViewObject, #"that's a nice view"
myUIViewController, #"not really special"
myOtherObject, #"very important one"
this list can be extended and modified at any time, so I thought about using NSMutableDictionary here. But I am not really sure...
The object should be the key, i.e. I want to find easily the matching string for myUIViewController or myOtherObject when I ask for it like so:
- (NSString*)checkObjNoteStringForObject:(id)anyObjectInList;
The other problem is, that when an object gets added to that "list", I don't want it to be retained because of that. NSMutableDictionary retains it's contents, right? Could I just send a -release afterwards to undo this unwanted behaviour, and when removing from the list just sending -retain before doing so? Or is there a more elegant way?
What do you suggest? Thanks # all!
If your dictionary key is not retained, once it is deallocated accesses to the dictionary will lead to undefined behaviour (in practice, they'll crash if a lookup happens to hit that dictionary element). To do what you want, you need a strategy to remove the objects from the dictionary when necessary.
If you do have one – for instance, overriding the objects’ -dealloc and removing them from there – you can do what you want using +[NSValue valueWithNonretainedObject:]. The NSValue will refer to your object without retaining it, and the dictionary will copy the NSValue (keys are copied, not retained). Just remember to create an NSValue for each time you want to look something up in the dictionary; a helper function or method is a good idea.