【问题标题】:Can I override setValue: forKey:?我可以覆盖 setValue: forKey: 吗?
【发布时间】:2013-08-22 02:18:48
【问题描述】:

我有一个可能将 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] 被设置时,该方法不会被调用。我怎样才能使它成为我的设置器的默认行为?

【问题讨论】:

    标签: objective-c key-value-coding


    【解决方案1】:

    肯定有办法做到这一点。您可以声明相关属性@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,而不是在运行时进行(这将是一种单行方法)。我使用运行时的唯一原因是如果有一堆属性,并且对象非常简单(纯粹是一个数据对象)。

    【讨论】:

    • 虽然有用,但我不认为这实际上回答了有关 -setValue:forKey: 可覆盖性的问题。
    【解决方案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。

    【讨论】:

    • 它将被设置为NSNull 和nil。相同关键路径的任何其他观察者仍将看到NSNull。
    • 他们会看到[NSNull null],然后是nil。实现所需行为的唯一其他方法是为相关对象的所有属性设置自定义设置器。您可以实现这样的方法,使得没有观察者将NSNull 视为一个值,但这比基于 KVO 的解决方案的开销要大得多。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多