Thursday, April 28, 2011

Using class as key in NSDictionary.

I'm writing a contextual "factory" that will maintain a dictionary of converter/acting objects which inherit from some Converter class. This class has a method:

- (Class)classResponsibility

Or something similar, such that a StringConverter class would implement the method as:

- (Class)classResponsibility {
    return [NSString class];
}

Then to store that converter in the dictionary, I had hoped on doing something like:

[converters setValue:stringConverter forKey:[stringConverter classResponsibility]];

But the compiler complains that the type "Class" is an invalid parameter type for argument 2 of the setValue:forKey: method. I had wanted to avoid setting the key as the Class's name ("NSString"), but if that's the best solution than I'll go with it.

From stackoverflow
  • -setValue:forKey: is documented to take an NSString as the second parameter. You'll have to use NSStringFromClass() and NSClassFromString() as adaptors.

    Jarret Hardie : You are right about Class being an object
    Jarret Hardie : Don't the docs for setValue:forKey: say that the key only has to be an object when using key-value coding? Otherwise, couldn't it be any object that implements NSCopying protocol, like NSDictionary?
  • I was looking for the setObject:forKey: method instead of setValue:forKey:. The method signature for setObject:forKey: accepts (id) as both parameter types, and is much better suited.

    Graham Lee : Classes don't implement NSCopying, so can't be used as the key in -setObject:forKey:. Really that parameter should be (id ), I'm not sure why it's not.
  • A class object (type Class) isn't actually an Objective-C object; it's a pointer to an opaque objc_class struct (you can see this in 'objc.h'). Because the keys to an NSDictionary have to be objects conforming to the NSCopying protocol, a Class isn't going to work for a key.

    The simplest thing to do is call NSStringFromClass and use the name of the class as the key, as Graham Lee suggested.

    The difference between setObject:forKey: and setValue:forKey: is that the latter is part of the key-value coding infrastructure. In general you'll probably want to prefer setObject:forKey: when dealing with NSMutableDictionary as it carries less semantic baggage and doesn't require your keys to be NSStrings (that's my opinion; in practice I suppose it's really more a matter of style).

  • I just had a similar situation crop up with the exact same error message:

    [tempDictionary setObject:someDictionary forKey:someClass];
    

    All I did was implement the NSCopying protocol in someClass:

    - (id)copyWithZone:(NSZone *)zone
    {
        id copy = [[[self class] allocWithZone:zone] init];
        [copy setId:[self id]];
        [copy setTitle:[self title]];
        return copy;
    }
    

    I think what was happening was that a copy of someClass was being made in order to be used as the key, but since my object didn't know how to copy itself (deriving from NSObject it didn't have a copyWithZone in the superclass) it balked.

    One thing I've found with my approach is that it's use an object as a key. Unless I already have the object instantiated, I'm constantly calling allKeys or just otherwise enumerating over the dictionary.

    [After writing this, I see that you want to store the class as such as the key. I'm leaving this out there because I would have saved a lot of time if I had found my answer when I was searching SO. I didn't find anything like this then.]

  • Your other option is to use [NSValue valueWithNonretainedObject:yourObjectHere] to construct the key from something other than a string. I ran into a similar problem and I wanted to use a CoreData object as the key and something else as the value. This NSValue method worked perfect and I believe was it's original intent. To get back to the original value just call nonretainedObjectValue

    craig : Very interesting, thanks for that. I've unfortunately already gone down the other path, but in the future I will remember to give this a try.

0 comments:

Post a Comment