KVO的基本原理大概是这样的
当一个对象被观察时, 系统会新建一个子类NSNotifying_A ,在子类中重写了对象被观察属性的 set方法, 并且改变了该对象的 isa 指针的指向(指向了新建的子类) , 当属性的值发生改变了, 会调用子类的set方法, 然后发出通知
一. KVO 的基本使用
给_person对象 添加观察者self, 当person对象的name的值发生改变的时候, 会触发observer方法
_person = [Person new]; p.name = @"oldName";
//添加观察者
// [p addObserver:self forKeyPath:@"dog" options:NSKeyValueObservingOptionNew context:nil];
[_person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
// 所观察的对象的keyPath 发生改变的时候, 会触发 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ NSLog(@"%@",keyPath); NSLog(@"%@",change); }
二. 当keyPath 为对象时, 改对象有许多属性, 怎么办?
在person类中,重写这个方法, 设置需要观察的属性 , 注意:"_dog.level"
//返回一个容器, 里面放的是NSString类型 + (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{ NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key]; //观察dog对象中的所有属性 if ([key isEqualToString:@"dog"]) { keyPaths = [keyPaths setByAddingObjectsFromArray:@[@"_dog.level",@"_dog.age"]]; } return keyPaths; }
三. 手动触发KVO
系统默认该对象的所有属性 都能被观察到 ,重写下面方法, 可以单独设置某个属性不能被观察
//默认 yes, 默认自动观察所有属性 + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{ return YES; }
//返回NO, 则不能被默认观察到name + (BOOL)automaticallyNotifiesObserversOfName{ return NO; } + (BOOL)automaticallyNotifiesObserversOfAge{ return YES; }
当 name 发生改变的时, 手动触发, 发送通知
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ //手动发通知 //即将改变(发一次通知) [_person willChangeValueForKey:@"name"]; _person.name = @"dddd"; //已经改变(发一次通知),一共发了两次通知 [_person didChangeValueForKey:@"name"];
}
四. 自定义KVO
根据kvo的原理, 可以自定义一个kvo, 建一个NSObject的分类, 添加方法
@interface NSObject (XSKVO) - (void)xs_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context; @end
通过runtime的方式, 动态创建一个类, 并给该类添加方法
#import "NSObject+XSKVO.h" #import <objc/runtime.h> @implementation NSObject (XSKVO) - (void)xs_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{ //1.新建一个类 NSString *className = [@"XSKVO" stringByAppendingString: NSStringFromClass([self class])]; Class newClass = objc_allocateClassPair([self class], className.UTF8String, 0); //注册类 objc_registerClassPair(newClass); //2.修改 调用者类型 object_setClass(self, newClass); //3.给子类添加set方法(子类里面没有set方法的) //OC方法:方法编号SEL ,方法实现IMP class_addMethod(newClass, @selector(setName:), xssetName, ""); } /* 隐藏的参数: self 方法的调用者 _cmd 方法的编号 */ void xssetName(id self,SEL _cmd, NSString *newName){ NSLog(@"自定义的实现%@",newName); //方案一:通过消息机制 发送消息 -observeValueForKeyPath } @end
五. 其他
关于容器类(如:NSMutableArray)的观察, 当通过addObject: 向数组中添加对象, 不会触发KVO, 因为并没有触发set方法,
解决方法: 通过KVC 方法 - mutableArrayValueForKey: