如何在运行时向一个对象添加属性?

41

在运行时给Objective-C对象添加属性是否可行?


如果符合KVC协议,那么就有可能实现。 - Nekto
你所说的“properties”具体指什么?是指Objective-C中声明的属性吗? - user557219
3个回答

62
通过class_addProperty(),可以为类添加正式属性:
BOOL class_addProperty(Class cls,
    const char *name,
    const objc_property_attribute_t *attributes,
    unsigned int attributeCount)

第一和第二个参数不言自明。第三个参数是一个属性属性数组,每个属性属性都是遵循Objective-C type encodings for declared properties的名称-值对。请注意,文档仍然提到了用于属性属性编码的逗号分隔字符串。逗号分隔字符串中的每个段由一个objc_property_attribute_t实例表示。此外,objc_property_attribute_t还接受类名,而不仅仅是通用的@类型编码的id
以下是动态向已经有一个名为_privateName的实例变量的类添加一个名为name的属性的程序的初稿:
#include <objc/runtime.h>
#import <Foundation/Foundation.h>

@interface SomeClass : NSObject {
    NSString *_privateName;
}
@end

@implementation SomeClass
- (id)init {
    self = [super init];
    if (self) _privateName = @"Steve";
    return self;
}
@end

NSString *nameGetter(id self, SEL _cmd) {
    Ivar ivar = class_getInstanceVariable([SomeClass class], "_privateName");
    return object_getIvar(self, ivar);
}

void nameSetter(id self, SEL _cmd, NSString *newName) {
    Ivar ivar = class_getInstanceVariable([SomeClass class], "_privateName");
    id oldName = object_getIvar(self, ivar);
    if (oldName != newName) object_setIvar(self, ivar, [newName copy]);
}

int main(void) {
    @autoreleasepool {
        objc_property_attribute_t type = { "T", "@\"NSString\"" };
        objc_property_attribute_t ownership = { "C", "" }; // C = copy
        objc_property_attribute_t backingivar  = { "V", "_privateName" };
        objc_property_attribute_t attrs[] = { type, ownership, backingivar };
        class_addProperty([SomeClass class], "name", attrs, 3);
        class_addMethod([SomeClass class], @selector(name), (IMP)nameGetter, "@@:");
        class_addMethod([SomeClass class], @selector(setName:), (IMP)nameSetter, "v@:@");

        id o = [SomeClass new];
        NSLog(@"%@", [o name]);
        [o setName:@"Jobs"];
        NSLog(@"%@", [o name]);
    }
}

它的(修剪后的)输出:

Steve
Jobs

获取器和设置器方法应该更加小心地编写,但这已经足够作为在运行时动态添加正式属性的示例。


1
class_addProperty返回true,但class_getInstanceVariable始终返回nil。我已尝试将属性名称放入ivar名称中,但仍然没有运气。有任何想法是什么问题? - Mercurial
1
@Bavarious,你是怎么愚弄编译器的?我的意思是[o name]导致编译错误“没有已知的选择器'name'实例方法”。 - Hitesh Savaliya
@HiteshSavaliya 很久以前(在ARC之前),这是可能的。现在,您至少需要声明-name选择器。 - Michael
1
@PercevalFARAMAZ:是的,因为ARC主要是编译时特性...而-performSelector:已经被声明并且对编译器可见。在ARC之前,你可以不用-performSelector:来做到这一点,但在ARC下就不行了。这就是我说的,它仍然是正确的。有100种方法可以规避每个ARC限制,例如调用[self performSelector:NSSelectorFromString(@"retain")]代替[self retain]。ARC是一个安全特性,而不是安全特性!在我看来,-performSelector并不比声明选择器更优雅。 - Michael
1
@PercevalFARAMAZ:如果你在编译时不知道选择器,无论是否使用ARC,你都必须使用类似于-performSelector:的东西。这里的讨论是关于[o name]导致编译器错误“没有已知的实例方法选择器'name'”,这是一个ARC问题。没有ARC,调用[o name]是可行的,这就是我说的。使用ARC,你必须做一些不同的事情,比如-performSelector:,或者声明-name选择器。明白了吗? - Michael
显示剩余3条评论

9

如果您查看文档这里所记录的NSKeyValueCoding协议,您会发现有一个称为:

- (id)valueForUndefinedKey:(NSString *)key

您应该重写该方法,为指定的未定义属性提供自定义结果。当然,这是建立在您的类使用相应协议的前提下。
这种方法通常用于为类提供未知行为(例如不存在的选择器)。

4

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