我可以覆盖setValue:forKey:?

时间:2013-08-22 02:18:48

标签: objective-c key-value-coding

我有一个对象很可能将NSNull设置为其所有属性的值,并且我正在处理与NSNull值不兼容的代码。是否有可能覆盖setValue:forKey:在我这样的对象中?

-(void)setValue:(id)value forKey:(NSString *)key {

    if ([value isEqual:[NSNull null]]) {
        [super setValue:nil forKey:key];
    } else {
        [super setValue:value forKey:key];
    }
}

object.property = [NSNull null]设置为{{1}}时,不会调用该方法。如何将此作为我的setter的默认行为?

2 个答案:

答案 0 :(得分:5)

肯定有办法做到这一点。您可以声明相关属性@dynamic,然后使用+resolveInstanceMethod:实际按需创建setter。您可以在sample code for iOS:PTL中找到这样的示例。此示例演示如何自动使访问者读取和写入字典(properties)而不是来自ivars。为了使这项工作成为可能,您可能需要始终将NSNull存储在字典中并覆盖getter(propertyIMP())以将其转换为nil。所以你要改变这个:

[[self properties] setValue:value forKey:key];

为:

[[self properties] setValue:(value ?: [NSNull null]) forKey:key];

并改变:

return [[self properties] valueForKey:NSStringFromSelector(_cmd)];

为:

id value = [[self properties] valueForKey:NSStringFromSelector(_cmd)];
return (value == [NSNull null] ? nil : value);

或类似的东西。

但是......除非这是一次重大胜利,否则我会避免这种魔力。我对此的典型解决方案是采用另一种方式,并将其放在调用方上,而不是在它们不应该通过时[NSNull null]。我使用像RNNonNull()这样的函数:

id RNNonNull(id x) { return (x == [NSNull null]) ? nil : x; }

然后调用者负责添加包装器:

obj.foo = RNNonNull(mystuff);

如果那是不可能的,我可能会手动覆盖getter以将NSNull转换为nil而不是使用运行时(它是单行方法)。我使用运行时的唯一原因是,如果有一堆属性,并且对象非常简单(纯粹是数据对象)。

答案 1 :(得分:2)

设置属性实际上不会导致调用setValue:forKey:。你可以用KVO完成这个任务。例如,

@interface Foo
@property (nonatomic, strong) NSObject *bar;
@end

@implementation

- (id)init {
    self = [super init];
    if (self) {
        [self addObserver:self forKeyPath:@"bar" options:0 context:nil];
    }
    return self;
}

- (void)dealloc {
    [self removeObserver:self forKeyPath:@"bar"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if (self.bar == [NSNull null]) {
        self.bar = nil;
    }
}

因此,只要将bar属性设置为[NSNull null,它就会立即设置为nil