在NSDictionary中使用类作为键

37

我正在编写一个上下文“工厂”,该工厂将维护一个继承自某个转换器类的转换器/操作对象字典。该类具有一个方法:

- (Class)classResponsibility

或者类似的东西,使得一个StringConverter类可以实现如下方法:

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

那么将该转换器存储在字典中,我希望可以这样实现:

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

但编译器抱怨类型 "Class" 是 setValue:forKey: 方法的第二个参数无效的参数类型。我本来想避免将键设置为类的名称("NSString"),但如果这是最好的解决方案,那么我会使用它。

7个回答

100
您正在使用的是setValue:forKey:,该方法仅接受NSString作为键。您应该改用setObject:forKey:。类对象(指向类对象的指针可以作为类型Class传递)是一个完整的Objective-C对象(类对象是其元类的实例,并且您可以在类对象上使用所有的NSObject方法;在这里了解更多关于元类的信息),因此它们可以在任何需要对象的地方使用。
字典的另一个要求是键支持复制(即具有copyWithZone:方法)。类对象是否支持此方法?事实上,它确实支持。NSObject类定义了类方法+copyWithZone:,其文档明确指出它“让您将类对象用作NSDictionary对象的键”。我想这就是您问题的答案。

23
从XCode 4.5开始,为了避免警告,您需要首先将其转换为以下形式:[dictionary setObject:object forKey:(id <NSCopying>)someClass];请参见此答案https://dev59.com/OWcs5IYBdhLWcg3w3HkG#12525479。 - odyth
你知道为什么键必须符合 NSCopying 的技术原因吗? - Drux
1
@Drux 我的猜测是NSDictionary不想保留那些可能被其他人释放的对象,当它们不再需要时。如果NSDictionary保留了该对象,而其他人释放了它,则该对象仅由NSDictionary实例作为键保留,并且当键的值被删除时,该对象也会被删除。因此,复制使键对象仅对NSDictionary可用(如果您查看allKeys方法,它还返回键数组的副本)。 - Eimantas
@Drux 我怀疑NSDictionary的实现对于键一旦被设置就不会发生变异做出了一些优化假设。 - Frank Schmitt
请注意,如果自动完成不显示“-setObject:forKey”作为选项,则可能已声明(不可变的)NSDictionary而不是NSMutableDictionary。 - Frank Schmitt
1
@Drux Frank部分正确,如果键的哈希值发生变化,查找将会失败。键必须是不可变的。 - jasongregori

30

你的另一个选择是使用[NSValue valueWithNonretainedObject:yourObjectHere]来构建与字符串以外的对象作为键的内容。我曾遇到类似问题,想要将CoreData对象作为键,其他内容作为值。这个NSValue方法非常适用,并且我相信它的原始意图就是如此。要恢复原始值,只需调用nonretainedObjectValue即可。


1
非常有趣,谢谢。不幸的是,我已经选择了另一条路,但将来我会记得尝试这个方法。 - Craig Otis
非常棒的替代方案,而不是为我在数百个类中实现copyWithZone :) - Will

5

虽然类对象在NSDictionary中是一个完美的键,但值得一提的是NSMapTable,它是基于NSDictionary模型的,但提供了更多灵活性,可以使用什么样的对象作为键和/或值,文档支持弱引用和任意指针。


这应该是iOS 6及以上的新首选解决方案。 - James Kuang
你能在NSMapTable中使用weak作为class key吗?有可能会悄悄地死掉吗? - pronebird

4

-setValue:forKey:的文档说明第二个参数需要传入一个NSString类型。你需要使用NSStringFromClass()NSClassFromString()这两个适配器。


setValue:forKey:的文档不是说在使用键值编码时键只需要是一个对象吗?否则,它不可以是任何实现NSCopying协议的对象,比如NSDictionary吗? - Jarret Hardie

3

我正在寻找 setObject:forKey: 方法而不是 setValue:forKey:. setObject:forKey: 方法的方法签名接受 (id) 作为两个参数类型,并且更加适合。


@Graham Lee:类实现了NSCopying。请参阅+copyWithZone:方法的文档:http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSObject_Class/Reference/Reference.html#//apple_ref/doc/uid/20000050-copyWithZone_ - user102008

1

我刚遇到了一个类似的情况,出现了完全相同的错误信息:

[tempDictionary setObject:someDictionary forKey:someClass];

我所做的只是在someClass中实现了NSCopying协议:
- (id)copyWithZone:(NSZone *)zone
{
    id copy = [[[self class] allocWithZone:zone] init];
    [copy setId:[self id]];
    [copy setTitle:[self title]];
    return copy;
}

我认为发生的事情是,为了用作键而制作了someClass的副本,但由于我的对象不知道如何复制自己(从NSObject派生它没有在超类中有一个copyWithZone),所以它失败了。

我发现我的方法之一是将对象用作键。除非我已经实例化了对象,否则我会不断调用allKeys或者只是枚举字典。

[写完这篇文章后,我看到你想将类本身存储为键。我把它留在这里,因为如果当时我在搜索SO时找到了答案,我会节省很多时间。那时我什么都没找到。]


我今天刚遇到这个问题,但是另一个类已经运行了很多月并且没有问题,我不明白为什么。有人知道原因吗?“Model”是一个自定义类,只继承自NSObject(不符合NSCoding规范)`static NSMutableDictionary *singletonInstances;@implementation SingletonModel+(void)initialize { singletonInstances = [[NSMutableDictionary alloc] initWithCapacity:10]; }+(void)setSharedModel:(Model*)aModel { if (![singletonInstances objectForKey:[aModel class]]) { [singletonInstances setObject:aModel forKey:[aModel class]]; } }` - Rafael Nobre
@nobre:类对象可以用作键。请参见http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSObject_Class/Reference/Reference.html#//apple_ref/doc/uid/20000050-copyWithZone_。 - user102008
这篇文章对实现“NSCopying”可能遇到的问题进行了深入的讨论:http://robnapier.net/blog/implementing-nscopying-439 - penfold

1
您可以像这样使用类作为NSDictionary的键:
@{
    (id)[MyClass1 class] : @1,
    (id)[MyClass2 class] : @2,
    (id)[MyClass3 class] : @3,
    (id)[MyClass4 class] : @4,
};

甚至可以像这样:

或者像这样:

@{
    MyClass1.self : @1,
    MyClass2.self : @2,
    MyClass3.self : @3,
    MyClass4.self : @4,
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接