【问题标题】:Objective-C pattern for class instance variables?类实例变量的Objective-C模式?
【发布时间】:2011-08-23 09:55:50
【问题描述】:

对于可以被子类“覆盖”的类变量,Objective-C 中的一个很好的模式是什么?

常规类变量通常在 Objective-C 中使用文件本地静态变量以及定义为类方法的公开访问器来模拟。

但是,与任何 Class 变量一样,这意味着该值在该类及其所有子类之间共享。有时,子类只为自己更改值是很有趣的。这通常是使用类变量进行配置时的情况。

这里有一个例子:在一些 iOS 应用程序中,我有许多给定公共抽象超类(注释)的对象,这些对象有许多具体的变体(子类)。所有注释都用标签以图形方式表示,标签颜色必须反映其注释的特定种类(子类)。所以所有的Foo注解都必须有一个绿色的标签,所有的Bar注解都必须有一个蓝色的标签。在每个实例中存储标签颜色会很浪费(实际上,可能是不可能的,因为我有很多对象,而且每个实例共有的实际配置数据远大于单一颜色)。

在运行时,用户可以决定现在所有的 Foo 注释都将带有红色标签。以此类推。

由于在 Objective-C 中,类是实际的对象,这需要将 Foo 标签颜色存储在 Foo 类对象中。但这可能吗?这种事情的好模式是什么?当然,可以定义某种全局字典,将类映射到其配置值,但这有点难看。

【问题讨论】:

  • 为什么不简单地覆盖子类中的类方法?
  • 然后返回另一个常量?那么用户如何更改 Foo 标签颜色呢?在那种情况下返回另一个全局的值?那么对于每个子类,您定义的新全局变量与配置变量一样多吗?是的,我猜它会起作用。但是很丑。
  • Obj-C 没有真正的类变量。查看 Objective-C 编程语言指南中的 Variables and Class Objects chapter 了解一些可以使用的模式。
  • 我知道那个文件。正如我所写的,虽然语言中缺少常规类变量,但使用全局变量很容易模拟。但这不是我在这里所追求的。我正在寻找模拟类实例变量的模式,您引用的文档甚至没有提及也没有以任何方式承认(AFAICS)。
  • 我不确定它们是否适用于类对象,但也许associative references 可能是模拟这些对象的一种方式。

标签: objective-c class variables objective-c-runtime class-instance-variables


【解决方案1】:

当然,可以定义某种全局字典,将类映射到其配置值,但这有点难看。

为什么你认为这会很丑?这是一种非常简单的方法,因为您可以使用[self className] 作为字典中的键。使它持久化也很容易,因为您可以简单地将字典存储在 NSUserDefaults 中(只要它只包含属性列表对象)。您还可以通过调用 superclass 方法让每个类默认为其超类的值,直到找到具有值的类。

+ (id)classConfigurationForKey:(NSString *)key {
    if(_configurationDict == nil) [self loadConfigurations]; // Gets stored values
    Class c = [self class];
    id value = nil;
    while(value == nil) {
        NSDictionary *classConfig = [_configurationDict objectForKey:[c className]];
        if(classConfig) {
            value = [classConfig objectForKey:key];
        }
        c = [c superclass];
    }
    return value;
}
+ (void)setClassConfiguration:(id)value forKey:(NSString *)key {
    if(_configurationDict == nil) [self loadConfigurations]; // Gets stored values
    NSMutableDictionary *classConfig = [_configurationDict objectForKey:[self className]];
    if(classConfig == nil) {
        classConfig = [NSMutableDictionary dictionary];
        [_configurationDict setObject:classConfig forKey:[self className]];
    }
    [classConfig setObject:value forKey:key];
}

此实现不提供检查以确保您不会越过顶级超类,因此您需要确保该类有一个值以避免无限循环。

如果要存储无法存储在属性列表中的对象,可以在访问字典时使用一种方法来回转换。下面是一个访问 labelColor 属性的示例,该属性是一个 UIColor 对象。

+ (UIColor *)classLabelColor {
    NSData *data = [self classConfigurationForKey:@"labelColor"];
    return [NSKeyedUnarchiver unarchiveObjectWithData:data];
}
+ (void)setClassLabelColor:(UIColor *)color {
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:color];
    [self setClassConfiguration:data forKey:@"labelColor"];
}

【讨论】:

  • 您过分关注示例用例而不是核心问题(类实例变量),但您的解决方案只需稍作修改即可解决此问题。重命名您的方法,使用MyType aValue = [[self class] instanceVarForKey:@"varName"]; 不会那么糟糕。我仍然觉得它很难看,因为它需要 two 字典查找。当然,您的解决方案也只能用于 Object 类型,不能区分不存在的类实例变量和 nil 值,并且没有任何类型检查(恐怕最后一点可能是不可避免的)。
【解决方案2】:

我的回答可能会有所帮助:

What is the recommended method of styling an iOS app?

在这种情况下,您的注释只包含对样式的引用(例如,每种样式只需要一个),并且整个样式的指针大小也不错。无论哪种方式,该帖子都可能会给您一些想法。

更新

Jean-Denis Muys:这解决了我的问题的示例用例,但不是我的问题本身(模拟类实例变量的模式)。

你说得对,我不知道你的例子对你的问题的建模有多接近,我考虑过对此发表评论。

对于更通用和可重用的解决方案,如果您的全局数据不重要(正如您在 OP 中提到的那样),我可能只会编写一个线程安全的全局字典。你可以在+initialize 中填充它,或者通过引入一个类方法来懒惰地填充它。然后您可以向NSObject 添加一些类别来访问和改变静态数据——这样做是为了便于语法。

我认为这种方法的好处是您可以在任何程序中重用它(即使它看起来很难写或写起来很复杂)。如果锁定过多,那么您可能希望将字典按前缀划分或创建一个简单的线程安全字典,您的类包含对它的引用——然后您可以通过 objc 运行时合成一个实例变量来存储它并声明一个实例方法访问它。类方法仍然必须直接使用全局数据接口。

【讨论】:

  • 是的,很有趣。这解决了我的问题的示例用例,但不是我的问题本身(模拟类实例变量的模式)。
猜你喜欢
  • 2011-05-07
  • 2013-02-02
  • 2023-04-04
  • 2012-02-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多