直接上简化后的KVO实现代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
ZYObject *obj = [[ZYObject alloc]init];
ZYObserver *observer = [[ZYObserver alloc]init];
obj.name = @"value1";
obj.age = 26;
[obj addObserver:observer forKeyPath:KVOClassKeyPath(ZYObject,name) options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld usingBlock:^(NSString *keyPath, id object, NSDictionary<NSKeyValueChangeKey,id> *change) {
NSLog(@"\nkeyPath:%@\nobject:%@\nchange:%@",keyPath,object,change);
}];
[obj addObserver:observer forKeyPath:KVOClassKeyPath(ZYObject,name) usingBlock:^(id newValue, id oldValue) {
NSLog(@"\nnewValue:%@\noldValue:%@\n",newValue,oldValue);
}];
NSSet *set = [NSSet setWithArray:@[KVOClassKeyPath(ZYObject,name),KVOClassKeyPath(ZYObject,age)]];
[obj addObserver:observer forKeyPathSet:set options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld usingBlock:^(NSString *keyPath, id object, NSDictionary<NSKeyValueChangeKey,id> *change) {
NSLog(@"\nkeyPath:%@\nobject:%@\nchange:%@",keyPath,object,change);
}];
obj.name = @"value2";
observer.name = @"value3";
obj.age = 27;
}
return 0;
}
实现原理:
设计一个辅助类
KVOController
去管理Observer
所有的观察实现,这个KVOController
通过运行时添加为Observer
的属性,在Observer
调用dealloc
之前,KVOController
会先调用自己的dealloc
方法,在这个方法中,移除Observer
的所有观察对象.
上实现代码:
NSObject+KVOController.h
#import <Foundation/Foundation.h>
#import "KVOController.h"
#define KVOClassKeyPath(CLASS, KEYPATH) \
@((((CLASS *)(nil)).KEYPATH, #KEYPATH))
@interface NSObject (KVOController)
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options usingBlock:(kvoCallBack)block;
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath usingBlock:(kvoBriefCallBack)block;
- (void)addObserver:(NSObject *)observer forKeyPathSet:(NSSet <NSString *>*)keyPathSet options:(NSKeyValueObservingOptions)options usingBlock:(kvoCallBack)block;
@end
NSObject+KVOController.h
#import "NSObject+KVOController.h"
#import "KVOController.h"
#import <objc/runtime.h>
@implementation NSObject (KVOController)
- (KVOController *)kvoController{
KVOController *obj = objc_getAssociatedObject(self, @selector(kvoController));
if (!obj) {
obj = [[KVOController alloc]init];
objc_setAssociatedObject(self, @selector(kvoController), obj, OBJC_ASSOCIATION_RETAIN);
}
return obj;
}
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options usingBlock:(kvoCallBack)block{
[observer.kvoController observe:self forKeyPath:keyPath options:options usingBlock:block];
}
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath usingBlock:(kvoBriefCallBack)block{
[self addObserver:observer forKeyPath:keyPath options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld usingBlock:^(NSString *keyPath, id object, NSDictionary<NSKeyValueChangeKey,id> *change) {
id newValue = change[NSKeyValueChangeNewKey];
id oldValue = change[NSKeyValueChangeOldKey];
if ([newValue isKindOfClass:[NSNull class]]) {
newValue = nil;
}
if ([oldValue isKindOfClass:[NSNull class]]) {
oldValue = nil;
}
block(newValue,oldValue);
}];
}
- (void)addObserver:(NSObject *)observer forKeyPathSet:(NSSet <NSString *>*)keyPathSet options:(NSKeyValueObservingOptions)options usingBlock:(kvoCallBack)block{
for (NSString *keyPath in keyPathSet) {
[self addObserver:observer forKeyPath:keyPath options:options usingBlock:block];
}
}
@end
KVOController.h
#import <Foundation/Foundation.h>
typedef void (^kvoCallBack)(NSString * keyPath,id object,NSDictionary<NSKeyValueChangeKey,id> * change);
typedef void (^kvoBriefCallBack)(id newValue,id oldValue);
@interface KVOController : NSObject
- (void)observe:(NSObject *)beObserved forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options usingBlock:(kvoCallBack)block;
- (void)observe:(NSObject *)beObserved forKeyPathSet:(NSMutableSet <NSString *>*)keyPathSet options:(NSKeyValueObservingOptions)options usingBlock:(kvoCallBack)block;
@end
KVOController.m
#import "KVOController.h"
@interface KVOUnitInfo : NSObject
@property (nonatomic,copy) NSString *keyPath;
@property (nonatomic,strong) NSObject *beObserved;
@property (nonatomic,copy,readonly) kvoCallBack block;
- (instancetype)initWithBlock:(kvoCallBack)block;
@end
@implementation KVOUnitInfo
- (instancetype)initWithBlock:(kvoCallBack)block{
self = [super init];
if (self) {
_block = [block copy];
}
return self;
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey,id> *)change
context:(void *)context{
if (_block) {
_block(keyPath,object,change);
}
}
@end
@interface KVOController ()
@property (nonatomic,strong) NSMutableDictionary *keyPathObserversDic;
@end
@implementation KVOController
- (instancetype)init
{
self = [super init];
if (self) {
_keyPathObserversDic = self.keyPathObserversDic;
}
return self;
}
- (NSMutableDictionary *)keyPathObserversDic{
if (!_keyPathObserversDic) {
_keyPathObserversDic = [NSMutableDictionary dictionary];
}
return _keyPathObserversDic;
}
- (void)observe:(NSObject *)beObserved forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options usingBlock:(kvoCallBack)block{
NSAssert(keyPath, @"keyPath shoule not be nil");
KVOUnitInfo *unit = [[KVOUnitInfo alloc]initWithBlock:block];
unit.keyPath = keyPath;
unit.beObserved = beObserved;
@synchronized(self.keyPathObserversDic) {
NSMutableArray *keyPathObservers = self.keyPathObserversDic[keyPath];
if (!keyPathObservers) {
keyPathObservers = [NSMutableArray array];
self.keyPathObserversDic[keyPath] = keyPathObservers;
}
[keyPathObservers addObject:unit];
}
[beObserved addObserver:unit forKeyPath:keyPath options:options context:NULL];
}
- (void)observe:(NSObject *)beObserved forKeyPathSet:(NSMutableSet <NSString *>*)keyPathSet options:(NSKeyValueObservingOptions)options usingBlock:(kvoCallBack)block{
for (NSString *keyPath in keyPathSet) {
[self observe:beObserved forKeyPath:keyPath options:options usingBlock:block];
}
}
- (void)_uninstallObserver{
for (NSString *keyPath in self.keyPathObserversDic.allKeys) {
NSArray *observers = self.keyPathObserversDic[keyPath];
for (KVOUnitInfo *unit in observers) {
[unit.beObserved removeObserver:unit forKeyPath:unit.keyPath];
}
}
}
- (void)dealloc{
[self _uninstallObserver];
}
@end
参考资料:KVOController
网友评论