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”,必写。