KVO
KVO(Key-Value-Observer)也就是观察者模式,是苹果提供的一套事件通知机制。允许对象监听另一个对象特定属性的改变,并在改变时接收到事件。
1、KVO使用
- 监听对象的某个属性
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- 实现非正式协议
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
- 移除观察者
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
注意:addObserver和removeObserver需要成对出现,苹果建议在init的时候添加观察者,delloc时移除观察者。
- 如果没有移除,那会在观察者释放后在次接收到KVO回调会造成crash
- 如果重复remove也会导致
NSRangeException
类型的Crash
防护
1.注意移出对象 匹配
2.内存野指针问题,一定要在对象销毁前移出观察者
3.可以使用第三方库BlockKit添加KVO,blockkit内部会自动移除Observer避免crash.
2、是否会出发KVO
示例代码1:
#import <Foundation/Foundation.h>
@interface PersonClass : NSObject
{
NSInteger _age;//成员变量
}
//属性
@property (nonatomic, assign) NSInteger age;
@end
self.person1 -> _age = 234;
直接修改成员变量,我们发现没有触发KVO,看后面原理介绍即可知道,没有调用属性的set方法,如果想触发KVO也可以,手动实现KVO的willChangeValueForKey和didChangeValueForKey方法即可。
示例代码2:
//PersonClass类
#import <Foundation/Foundation.h>
@class AnimalClass;
@interface PersonClass : NSObject
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, strong) AnimalClass *animal;
@end
//AnimalClass类中有一个name属性
@interface AnimalClass : NSObject
@property (nonatomic, copy) NSString *name;
@end
//(1)不会监听到改变
self.person1.animal.name = @"cat";
//(2)会监听到改变
AnimalClass *ani2 = [[AnimalClass alloc]init];
ani2.name = @"cat";
self.person1.animal = ani2;
-
(1)不会被kvo监听到 ,因为修改animal的name属性,只是调用了animal的setName方法,没有调用person1的setAnimal方法。
-
(2)当我们对animal这个属性进行监听时,调用的是person1的setAnimal方法。
是否触发KVO关键在于是否调用了set方法
3、KVO的实现原理
KVO
是通过isa-swizzling技术实现的。在运行时动态生成一个中间类(NSKVONotifying_xxx,xxx是原类名字),这个中间类是原类的子类,并动态修改当前对象的isa
指向中间类。
- 首先重写set方法。在set方法里分别调用willChangeValueForKey->set的赋值操作->didChangeValueForKey,在didChangeValueForKey内部会调用观察者的回调方法,返回被观察对象的相关参数。
- 中间类中重写
class
方法,返回原类的Class(PersonClass类)
。这是因为苹果不想暴露kvo的内部实现,建议在开发中不应该依赖isa
指针,而是通过class
实例方法来获取对象类型。 _isKVOA
方法,判断类是否使用KVO
的一个标记,想判断当前类是否是KVO
动态生成的类,可以从方法列表中搜索这个方法来判断
4、KVO的手动调用和自动调用
(1)自动调用
注册观察者、实现回调方法、移除观察者即可,观察的属性值变化后系统调用回调方法
(2)手动调用
因为调用KVO主要依靠两个方法,在属性发生改变之前调用willChangeValueForKey:
方法,在发生改变之后调用didChangeValueForKey:
方法,所以我们也通过这两个方法发起KVO调用即可,但是我们首先需要关闭系统的自动调用,即
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
BOOL automatic = NO;
if ([theKey isEqualToString:@"balance"]) {
automatic = NO;//表示手动实现KVO,关闭自动调用的KVO
}
else {
automatic = [super automaticallyNotifiesObserversForKey:theKey];
}
return automatic;
}
5、KVO的优缺点
优点:
- 提供一种简单的方法实现两个对象间的同步。例如:model和view之间同步;
- 可以在不改变原来对象类的代码情况下即可做出对该对象的状态变化进行监听)
- 用key paths来观察属性,因此也可以观察嵌套对象.
缺点:
- 需要手动移除观察者,不移除容易造成crash.
- 注册和移出成对匹配出现.
- keypath参数的类型String, 如果对象的成员变量被重构而变化字符串不会被编译器识别而报错.
6、KVO和Notification比较
KVO:被观察的对象直接向观察者法通知;绑定与特定的对象属性的值
NSNotification:一个中心对象对所有的观察者提供变更通知;主要从广义上关注程序事件。