我对使用键值观察 (KVO) 以及如何注册以接收属性更改通知感到满意:
[account addObserver:inspector
forKeyPath:@"openingBalance"
options:NSKeyValueObservingOptionNew
context:NULL];
但是,如果我想观察帐户对象的所有属性的变化,我该如何实现呢?我必须为每处房产登记通知吗?
我对使用键值观察 (KVO) 以及如何注册以接收属性更改通知感到满意:
[account addObserver:inspector
forKeyPath:@"openingBalance"
options:NSKeyValueObservingOptionNew
context:NULL];
但是,如果我想观察帐户对象的所有属性的变化,我该如何实现呢?我必须为每处房产登记通知吗?
似乎没有内置函数可以订阅对象所有属性的更改。
如果您不关心确切的属性已更改并且可以更改您的类,则可以向其添加虚拟属性以观察其他属性的更改(使用+ keyPathsForValuesAffectingValueForKey
或+keyPathsForValuesAffecting<Key>
方法):
// .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 通知(因此您不必对属性值进行硬编码) - 这样您将获得更改的属性如果需要,请命名。
正如 david van brink 所建议的,以下 Swift 代码为每个属性添加了观察结果。它有一个额外的功能来删除观察(例如 in 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)
}
}
}