【问题标题】:Writing my own @dynamic properties in Cocoa在 Cocoa 中编写我自己的 @dynamic 属性
【发布时间】:2010-08-24 19:37:45
【问题描述】:

假设(为了论证)我有一个包含 NSDictionary 的视图类。我想要一大堆属性,所有这些属性都可以访问该字典的成员。

例如,我想要@property NSString* title@property NSString* author

对于这些属性中的每一个,其实现都是相同的:对于 getter,调用 [dictionary objectForKey:propertyName];,对于 setter,对 setObject:forKey: 执行相同的操作。

编写所有这些方法需要大量时间并使用大量复制粘贴代码。有没有办法自动生成它们,就像 Core Data 对 NSManagedObject 子类的 @dynamic 属性所做的那样?需要明确的是,我只想要这种访问我在标题中定义的属性的方式,而不仅仅是任意键。

我遇到过 valueForUndefinedKey: 作为键值编码的一部分,它可以处理 getter,但我不完全确定这是否是最好的方法。

我需要这些是显式属性,以便我可以在 Interface Builder 中绑定到它们:我最终计划为此视图编写一个 IB 调色板。

(顺便说一句,我知道我使用 NSDictionary 存储这些的示例有点做作。我实际上正在编写 WebView 的子类,属性将引用 HTML 元素的 ID,但这对于逻辑并不重要我的问题!)

【问题讨论】:

    标签: objective-c cocoa


    【解决方案1】:

    在翻阅了 Objective-c 运行时文档后,我自己设法解决了这个问题。

    我实现了这个类方法:

    + (BOOL) resolveInstanceMethod:(SEL)aSEL
    {
        NSString *method = NSStringFromSelector(aSEL);
    
        if ([method hasPrefix:@"set"])
        {
            class_addMethod([self class], aSEL, (IMP) accessorSetter, "v@:@");
            return YES;
        }
        else
        {
            class_addMethod([self class], aSEL, (IMP) accessorGetter, "@@:");
            return YES;
        }
        return [super resolveInstanceMethod:aSEL];
    }
    

    后面是一对C函数:

    NSString* accessorGetter(id self, SEL _cmd)
    {
        NSString *method = NSStringFromSelector(_cmd);
        // Return the value of whatever key based on the method name
    }
    
    void accessorSetter(id self, SEL _cmd, NSString* newValue)
    {
        NSString *method = NSStringFromSelector(_cmd);
    
        // remove set
        NSString *anID = [[[method stringByReplacingCharactersInRange:NSMakeRange(0, 3) withString:@""] lowercaseString] stringByReplacingOccurrencesOfString:@":" withString:@""];
    
        // Set value of the key anID to newValue
    }
    

    由于此代码尝试实现在类上调用但尚未实现的任何方法,因此如果有人尝试调用您所期望的东西,则会导致问题。我计划添加一些完整性检查,以确保名称与我的预期相符。

    【讨论】:

    • 我要做的唯一调整是仅将第一个字母小写(并确保 _cmd 字符串至少包含 5 个字符): NSString *property = NSStringFromSelector(_cmd); NSString *firstLetter = [[property substringWithRange:NSMakeRange(3, 1)] lowercaseString]; property = [firstLetter stringByAppendingString:[property substringWithRange:NSMakeRange(4, [property length] - 5)]];
    • 很好的例子。我已经达到了一种冷冻 IMP(即对于setName:,我的 IMP 是setValueForName),但是你对NSStringFromSelector(_cmd) 的使用让我完全摆脱了我一直在寻找的精神裤子的刺激,将其概括为@ 987654326@。谢谢你写出来。
    【解决方案2】:

    您可以混合使用建议的选项:

    • 使用@dynamic 关键字
    • 覆盖 valueForKey: 和 setValue:forKey: 以访问字典
    • 使用objective-c 反射API 的方法class_getProperty 并检查它是否为零。如果它不是 nil 你的班级有这样的财产。如果是就不会。
    • 然后在不存在此类属性的情况下调用 super 方法。

    我希望这会有所帮助。可能看起来有点 hacky(使用反射),但实际上这是一个非常灵活且绝对“合法”的问题解决方案......

    PS:coredata 方式是可能的,但在你的情况下完全是矫枉过正......

    【讨论】:

    • 我想我会将 class_getProperty 位添加到我的解决方案中。谢谢。
    【解决方案3】:

    与宏成为朋友?这可能不是 100% 正确的。

    #define propertyForKey(key, type) \
        - (void) set##key: (type) key; \
        - (type) key;
    
    #define synthesizeForKey(key, type) \
        - (void) set##key: (type) key \
        { \
             [dictionary setObject];// or whatever \
        } \
        - (type) key { return [dictionary objectForKey: key]; }
    

    【讨论】:

      【解决方案4】:

      听起来您应该使用类而不是字典。您正在接近手动实现该语言试图为您提供的功能。

      【讨论】:

      • 是的,字典是一个人为的例子。我实际上是在使用它来创建一个可以通过绑定连接到的 HTML 文档的视图。你是对的,如果这就是我所做的一切,那将是一个糟糕的计划:)
      【解决方案5】:

      https://tobias-kraentzer.de/2013/05/15/dynamic-properties-in-objective-c/ 有一个不错的博客,其中包含示例代码,对动态属性进行了更强大的检查,Objective-C dynamic properties at runtime? 也是一个非常好的 SO 答案。

      关于答案的几点。可能想在接口中声明一个@property,以允许预先输入也可以在实现中将属性声明为动态的。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-08-14
        • 1970-01-01
        • 2013-06-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多