iOS 中 KVO, 是key-value-observing
的缩写, 是Objective-C 对观察者设计模式的一种实现,类似观察者设计模式的还有NSNotificationCenter
,不过一个是一对一(KVO
),一个是一对多(NSNotificationCenter
) ; 一般继承自NSObject
的对象都支持KVO. 日常开发中我们常常会监听数据模型的变化, 从而达到根据数据模型的修改对视图进行更新的要求;
KVO 常用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| /* 注册监听器 监听器对象为observer,被监听对象为消息的发送者即方法的调用者在回调函数中会被回传 监听的属性路径为keyPath支持点语法的嵌套 监听类型为options支持按位或来监听多个事件类型 监听上下文context主要用于在多个监听器对象监听相同keyPath时进行区分 添加监听器只会保留监听器对象的地址,不会增加引用,也不会在对象释放后置空,因此需要自己持有监听对象的强引用,该参数也会在回调函数中回传 */ - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context; eg: [obj addObserver:observer forKeyPath:@"value" options:NSKeyValueObservingOptionNew context:NULL]; /* 删除监听器 监听器对象为observer,被监听对象为消息的发送者即方法的调用者,应与addObserver方法匹配 监听的属性路径为keyPath,应与addObserver方法的keyPath匹配 监听上下文context,应与addObserver方法的context匹配 */ - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context; /* 监听器对象的监听回调方法 keyPath即为监听的属性路径 object为被监听的对象 change保存被监听的值产生的变化 context为监听上下文,由add方法回传 */ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context;
|
举个栗子 🌰
假如我的一个数据模型(student)的属性(age)发生了变化, 我需要在对应的视图进行修改这个属性的显示;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @property (nonatomic, strong) StudentModel *student; - (void)viewDidLoad { [super viewDidLoad]; _student = [[Student alloc] init]; _student.age = 10; [_student addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil]; [self requestData]; } - (void)requestData{ AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; [manager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { NSInteger age = [(NSDictionary *)responseObject[@"age"] integerValue]; _student.age = age; } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { }]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void *)context{ if ([keyPath isEqualToString:@"age"] && object == _student){ NSLog(@"当前年龄是: %ld",_student.age); } } - (void)dealloc{ [_student removeObserver:self forKeyPath:@"age"]; }
|
实现原理 🚀
Apple 使用 isa 混写技术实现 KVO , 当观察对象 testClass
的时候, KVO 会动态创建一个继承自testClass
的类 NSKVONotifying_testClass
并重写监听属性的 setter 方法,在调用 setter 的前后, 会观察对象属性的更改情况;子类拥有自己的 set 实现,
1 2 3
| [self willChangeValueForKey:@"value"]; _value = value; [self didChangeValueForKey:@"value"];
|
所以, 通过 set
方法进行赋值, 通过 KVC setValue:forKey:
两种方式都可以对 KVO 生效; 但是如果想通过对成员变量直接赋值的话, 需要手动添加 KVO 才可以生效
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| QKTestObject *obj = [[QKTestObject alloc] init]; QKTestObserver *observer = QKTestObserver.alloc.init; // 通过 KVO 监听 obj 的变化 [obj addObserver:observer forKeyPath:@"value" options:NSKeyValueObservingOptionNew context:NULL]; // 通过 setter 修改 value, 监听生效 // obj.value = 2; // 通过 kvo 修改 value , 监听生效 // [obj setValue:@4 forKey:@"value"]; // 通过成员变量赋值. 未执行监听方法中的输出 /* 成员变量需要手动 添加 KVO , 监听才会生效 [self willChangeValueForKey:@"value"]; _value += 1; [self didChangeValueForKey:@"value"]; */ [obj changeValue];
|
代码地址: