键值观察 - 如何观察对象的所有属性?

时间:2012-11-21 10:40:50

标签: objective-c key-value-observing

我对Key Value Observing(KVO)的使用感到满意,以及如何注册接收房产变更通知:

[account addObserver:inspector
          forKeyPath:@"openingBalance"
             options:NSKeyValueObservingOptionNew
              context:NULL];

但是,如果我想观察帐户对象的所有属性的变化,我该如何实现?我是否必须注册每个房产的通知?

2 个答案:

答案 0 :(得分:18)

似乎没有内置函数来订阅对象的所有属性的更改。

如果您不关心哪个属性已更改并且可以更改您的类,则可以向其添加虚拟属性以观察其他属性的更改(使用+ keyPathsForValuesAffectingValueForKey+keyPathsForValuesAffecting<Key>方法):< / p>

// .h. We don't care about the value of this property, it will be used only for KVO forwarding
@property (nonatomic) int dummy;

#import <objc/runtime.h>
//.m
+ (NSSet*) keyPathsForValuesAffectingDummy{

    NSMutableSet *result = [NSMutableSet set];

    unsigned int count;
    objc_property_t *props = class_copyPropertyList([self class], &count);

    for (int i = 0; i < count; ++i){
        const char *propName = property_getName(props[i]);
        // Make sure "dummy" property does not affect itself
        if (strcmp(propName, "dummy"))
            [result addObject:[NSString stringWithUTF8String:propName]];
    }

    free(props);
    return result;
}

现在,如果您观察dummy属性,每次更改对象的任何属性时,您都会收到KVO通知。

此外,您可以在发布的代码中获取对象中的所有属性的列表,并在循环中为每个属性订阅KVO通知(因此您不必硬编码属性值) - 这样您就可以如果需要,可以更改属性名称。

答案 1 :(得分:1)

下面的Swift代码添加了对每个属性的观察,正如David van Brink所建议的那样。它具有删除观察结果的附加功能(例如,在deinit中):

extension NSObject {
    func addObserverForAllProperties(
        observer: NSObject,
        options: NSKeyValueObservingOptions = [],
        context: UnsafeMutableRawPointer? = nil
    ) {
        performForAllKeyPaths { keyPath in
            addObserver(observer, forKeyPath: keyPath, options: options, context: context)
        }
    }

    func removeObserverForAllProperties(
        observer: NSObject,
        context: UnsafeMutableRawPointer? = nil
    ) {
        performForAllKeyPaths { keyPath in
            removeObserver(observer, forKeyPath: keyPath, context: context)
        }
    }

    func performForAllKeyPaths(_ action: (String) -> Void) {
        var count: UInt32 = 0
        guard let properties = class_copyPropertyList(object_getClass(self), &count) else { return }
        defer { free(properties) }
        for i in 0 ..< Int(count) {
            let keyPath = String(cString: property_getName(properties[i]))
            action(keyPath)
        }
    }
}