【问题标题】:How do I create a reusable class-level property construct in Objective-C?如何在 Objective-C 中创建可重用的类级属性构造?
【发布时间】:2014-04-24 20:54:12
【问题描述】:

我想要类级别的属性,我从https://stackoverflow.com/a/15811719/157384找到了解决方案

@interface Model
+ (int) value;
+ (void) setValue:(int)val;
@end

@implementation Model
static int value;
+ (int) value
{ @synchronized(self) { return value; } }
+ (void) setValue:(int)val
{ @synchronized(self) { value = val; } }
@end

现在您可以通过属性访问器进行调用,如下所示:

Model.value = 1
Model.value // => 1

太棒了!

现在,我想以某种形式的宏或元编程使这段代码可重用,它需要一个属性名称和类型。怎么写?

对于上面的示例,value(int)(和 Model)应该是动态的。

更新

感谢@Rich,我现在有了这个:

// Common.h
#define CLASS_PROPERTY_INTERFACE(TYPE, METHOD, CMETHOD) \
+ (TYPE) METHOD; \
+ (void) set##CMETHOD:(TYPE)val; \

#define CLASS_PROPERTY_IMPLEMENTATION(TYPE, METHOD, CMETHOD) \
static TYPE _##METHOD; \
+ (TYPE) METHOD \
{ @synchronized(self) { return _##METHOD; } } \
+ (void) set##CMETHOD:(TYPE)val \
{ @synchronized(self) { _##METHOD = val; } } \

// User.h
@interface User : NSObject
CLASS_PROPERTY_INTERFACE(User *, me, Me)
@end

// User.m
@implementation User
CLASS_PROPERTY_IMPLEMENTATION(User *, me, Me)
@end

User.me = currentUser;
User.me // => currentUser

如果可能的话,还有一件事是自动将传递给宏的方法名大写。

但它已经比现有的样板简洁得多了!

【问题讨论】:

  • 您能否通过使其可重用(或许使用伪代码)更详细地说明您的意思?你在寻找类似class_addProperty 的东西吗?
  • 我想说只使用 Xcode 代码 sn-p,而不是元编程,但如果你真的想要一个宏,请查看 libextobjc 以获得灵感。
  • 是否有可能在同一个头文件中有@interface@implementation
  • 它们可以在同一个文件中没有问题,@Rich。但是,将@implementation 部分放入一个文件中,该文件会导入到多个其他文件中,这会导致链接器错误。
  • @JoshCaswell 抱歉,这就是我的意思,在头文件中包含 @implementation(它必须与宏一起使用 - 这也将定义 @interface)不起作用,所以除非你像最初建议的那样使用代码 sn-p (或文件模板),否则我认为他不能做他想做的事......!

标签: objective-c cocoa metaprogramming


【解决方案1】:

宏观方式...

注意: 这有点可怕,我真的不推荐使用它,但正在尝试在宏中定义它们......

对此的警告(除了它不是很“好”)是 setter 方法的格式为 set_XXX: 格式。

#define CLASS_INTERFACE(CLS_NAME, METHOD, TYPE) @interface CLS_NAME : NSObject \
    + (TYPE) METHOD; \
    + (void) set_##METHOD:(TYPE)val; \
    @end \

#define CLASS_IMPLEMENTATION(CLS_NAME, METHOD, TYPE) @implementation CLS_NAME \
    static TYPE METHOD; \
    + (TYPE) METHOD \
    { @synchronized(self) { return METHOD; } } \
    + (void) set_##METHOD:(TYPE)val \
    { @synchronized(self) { METHOD = val; } } \
    @end \

将以下内容放在头文件中:

CLASS_INTERFACE(Test, value, int)

这在.m 文件中:

CLASS_IMPLEMENTATION(Test, value, int)

然后使用Test类:

[Test set_value:4];
int i = [Test value];

再一次,这很可怕,但会起作用......!

编辑:

单例

正如 cmets 中提到的,我认为使用单例更好,这让我编写了更可怕的代码:(

现在我们有(准备好病包了):

#define SINGLETON_INTERFACE_START(CLS_NAME) @interface CLS_NAME : NSObject \
    +(instancetype) sharedInstance; \

#define SINGLETON_INTERFACE_END(CLS_NAME) \
    @end \
    static inline CLS_NAME * CLS_NAME##Global () { return [CLS_NAME sharedInstance]; }

#define SINGLETON_IMPLEMENTATION(CLS_NAME) @implementation CLS_NAME \
    +(instancetype) sharedInstance \
    { \
        static id instance; \
        static dispatch_once_t onceToken; \
        dispatch_once(&onceToken, ^{ \
            instance = [self new]; \
        }); \
        return instance; \
    } \
    @end \

在你的标题中:

SINGLETON_INTERFACE_START(Test)

@property (atomic, assign) NSUInteger value;

SINGLETON_INTERFACE_END(Test)

在你的.m:

SINGLETON_IMPLEMENTATION(Test)

使用这个(是的,可以产生更多的病):

TestGlobal().value = 1;
int i = TestGlobal().value;

或者“更好”的 Objective-C 方式:

[Test sharedInstance].value = 1;
int i = [Test sharedInstance].value;

您甚至可以(美国队现在病了很多)为界面中的属性设置#define

请注意,我将@interface 中的@property 定义保留为atomic,因为OP 似乎喜欢使用@synchronized。这不是必需的,因为它们设置为 atomic

而且我知道 OP 想要类“属性”(只是说这让我颤抖),但还有其他 更好的 选项!

【讨论】:

  • 它的重点是避免[Test set_value:4],而是使用Test.value = 4。所以我们需要调整 setter 方法的大小写?
  • 你真的不应该为非属性类方法的 setter 调用 .。虽然它会起作用,但它看起来很糟糕!没有办法使用宏来做你想做的事情,除非它有 2 个参数 valuesetValue 用于宏,如果你愿意,我可以更新答案以显示。顺便说一句,为什么不能使用单例来代替这种奇怪的类方法呢?那样一切都会好很多……!
  • 我不建议经常使用单例,除非你真的需要走这条路,单一的 getter/setter 方法对作为类方法会更好,但如果你想要动态的东西,你可以发送消息单身人士可能是一个更好的主意。
  • 是的,我已经用一个暂定的解决方案更新了这个问题 - 仍然需要传递两个方法名称,一个用于 getter,另一个用于 setter。现在已经很接近了。 :)
  • @kenn 我已经用单例方法更新了我的答案,very 稍微好一点。至少你正确地使用了. 语法!而且您不必提供两个方法名称。
【解决方案2】:

这与要求在类上动态生成供实例使用的访问器没有什么不同。事实上,同样的问题。

无论对象的类型如何,直接对象值并不难做到,您可以通过利用关联对象在运行时纯粹生成它们。

但是,如果您需要使用超出 id 的类型信息来生成这些内容,则意味着您已将问题转变为编译时问题。

为此,您几乎可以编写一系列 #define 宏来生成您需要的任何代码。您至少需要两个宏;一个用于实现,一个用于接口。

【讨论】:

  • 感谢您指出这一点。我已经用一个暂定的解决方案更新了这个问题,但它需要同时传递方法名称和大写的方法名称。在我假设的宏中不能大写传递的参数?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-10-19
  • 1970-01-01
  • 1970-01-01
  • 2012-07-20
  • 2011-10-01
  • 1970-01-01
  • 2011-04-09
相关资源
最近更新 更多