KVO -2
观察者模式
当需要对一个对象的属性进行监听时
当对象某个属性(例如 A 中的字符串 name)发生更改时,对象会获得通知,并作
出相应处理
比如 View来监听Modal的变化而做出更改
系统KVO基本使用
添加观察者 :
addObserver: forKeyPath: options: context:
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options context:(nullable void
*)context;
实现观察响应方法 :
observeValueForKeyPath: ofObject: change: context:
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:
(nullable id)object change:(nullable NSDictionary*)change context:
(nullable void *)context;
移除观察者 :
removeObserver: forKeyPath:
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString
*)keyPath;
系统文件 :
我们进入KVO方法, 会跳转到系统的foundation框架文件
NSKeyValueObserving.h
@interface NSObject(NSKeyValueObserving)
observeValueForKeyPath: ...
@interface NSObject(NSKeyValueObserverRegistration)
addObserver 方法
removeObserver方法
@interface NSArray<ObjectType>(NSKeyValueObserverRegistration)
addObserver 方法
removeObserver方法
系统导入的头文件 :
同样也对NSSet.h / NSOrderedSet.h / NSDictionary.h /NSArray.h 进行扩展
方法
因此 :
1. 只要是上面几个类,或者继承自他们的子类, 均支持KVO 方法
2. 而绝大部分类都继承自NSObject
简单说 :
KVC 是系统在这些类上 扩展一些方法, 可以用来监听类属性值的改变 ,而具体实
现方法由系统完成
另外,由于KVO是基于key-value添加监听实现的, 那么KVO就是基于KVC 基础上
实现 , 而KVC 也是基于NSObject类上(后面介绍KVC)
系统KVO 原理 :
KVO 的实现依赖于 Objective-C 强大的 Runtime
观察一个对象A (类 YSUser) :
KVO 机制动态创建一个对象A当前类的子类YSUser_
实现 :
1. 创建一个 Person 类 内部有个 name 属性
声明变量 Person *personA = [[Person alloc]init];
2. 添加监听 :
[personA addObserver:self forKeyPath:@“name”
options:NSKeyValueObservingOptionNew context:nil]
3. 赋值 : personA.name = @“小明”;
4. 在observeValueForKeyPath: 回调方法收到了属性改变的通知
5. dealloc 方法中 移除监听 :
[personA removeObserver:self forKeyPath:@“name”];
系统做了啥 ?
第2步添加监听后 :
动态创建Person 子类 : NSKVONotifying_Person
继承自Person
让Person的isa类指针 指向NSKVONotifying_Person
重写方法 :
重写 setName方法
重写 class方法
返回Person父类 类名, 不让外界知道里面创建了新类(或者说是为了用户体验好,
统一显示Person)
重写 dealloc方法(父类子类都会调用dealloc, 不需要super)
做一些 KVO 释放内存
添加标识
_isKVO
第3步设置name值后 :
第一步 :
先调用了 willChangeValueForKey
第二步 :
再调用父类的 setter 方法(setName: ) , 对属性赋值
第三步 :
然后再调用 didChangeValueForKey 方法
并在 didChangeValueForKey 内部 调用监听器的 observeValueForKeyPath方
法 告诉外界 属性值发生了改变
这步结束后, 开发者就在observeValueForKeyPath: 回调方法收到了属性改变的
通知
KVO 使用常见问题 ?
1 : 结束时没有调用remove移除监听 (会导致崩溃)
2. 父类的监听回调被拦截 :
比如 :
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context {
if (object == _tableView && [keyPath
isEqualToString:@"contentOffset"]) {
[self doSomethingWhenContentOffsetChanges];
}
}
而是:
自己处理不了, 要让父类去处理 :
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context {
if (object == _tableView && [keyPath
isEqualToString:@"contentOffset"]) {
[self doSomethingWhenContentOffsetChanges];
} else {
[super observeValueForKeyPath:keyPath ofObject:object
change:change context:context];
}
}
2 : 重复remove 监听 导致崩溃
原因 :
如果父类,或者框架封装内部也在dealloc方法内移除的该属性的监听, 那么有可能
导致重复移除
解决 :
设置上下文 : context
1. 修改 添加监听 参数:
[personA addObserver:self forKeyPath:@“name”
options:NSKeyValueObservingOptionNew context:@“myPerson”];
2. 修改 移除监听 参数:
[personA removeObserver:self forKeyPath:@“name”
context:@“myPerson”];
3. 设置属性方式 :
_age = 3; 不会触发, 因为不会调用setAge: 方法
_name = @“小王”; 也不会触发监听
总结 :
1. KVO和线程
KVO 行为是同步的, 所有不要随意将方法执行丢到其它线程去
2. KVO 是通过 isa-swizzling 实现的
KVO 会自动为被观察对象创造一个派生类 , Objective-C 在发送消息的时候,会
通过 isa 指针找到当前对象所属的类对象。而类对象中保存着当前对象的实例方
法,因此在向此对象发送消息时候,实际上是发送到了派生类
(NSKVONotifying_ObjectA)对象的方法
3. 手动触发
先执行willChangeValueForKey:,再执行 didChangeValueForKey: , 才会发送
通知 ,
示例:
[self willChangeValueForKey:@"now"]; // “手动触发self.now的KVO”,必
写。
[self didChangeValueForKey:@"now"];// “手动触发self.now的KVO”,必写。