KVC
KVC全称Key-Value Coding,俗称“键值编码”,可以通过Key来访问某个属性,写在NSObject的NSKeyValueCoding分类下面。
常用方法
- (void)setValue:(nullable id)value forKey:(NSString *)key;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
- (nullable id)valueForKey:(NSString *)key;
- (nullable id)valueForKeyPath:(NSString *)keyPath;
setValue:forKey:
的原理
![](https://img.haomeiwen.com/i1181733/a60b376db55b3889.png)
valueForKey:
的原理
![](https://img.haomeiwen.com/i1181733/c46fc315e5539254.png)
使用场景
1.使用KVC访问修改私有变量、内部属性
2.字典模型转换
tips: KVC在使用中最常见的异常是使用了错误的key,或者传了nil。第一种情况重写setValue: forUndefinedKey:
可以避免crash,第二种情况重写setNilValueForKey:
。
KVO
KVO全程Key-Value Observing,俗称“键值观察”。
常用方法
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
实现原理
![](https://img.haomeiwen.com/i1181733/3d2ca0a9cd2e7711.png)
Person *p = [Person new];
NSLog(@"person添加KVO之前 - person:%@",object_getClass(p));
[p addObserver:self forKeyPath:@"age" options: NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld context:nil];
NSLog(@"person添加KVO之后 - person:%@",object_getClass(p));
// 2020-05-11 16:32:37.834314+0800 KVO[81741:2296042] person添加KVO之前 - person:Person
// 2020-05-11 16:32:37.834575+0800 KVO[81741:2296042] person添加KVO之后 - person:NSKVONotifying_Person
-
在运行时,系统会为调用
addObserver: forKeyPath: options: context:
方法的类,动态派生出一个名字是NSKVONotifying_xxx的子类,例如:Person 添加了观察者以后,动态派生出了NSKVONotifying_Person子类。 -
在子类中,重写了对应Key的set方法,为代码如下
- (void)setAge:(int)age { _NSSetIntValueAndNotify(); } // 伪代码 void _NSSetIntValueAndNotify() { [self willChangeValueForKey:@"age"]; [super setAge:age]; [self didChangeValueForKey:@"age"]; } - (void)didChangeValueForKey:(NSString *)key { // 通知监听器,某某属性值发生了改变 [oberser observeValueForKeyPath:key ofObject:self change:nil context:nil]; }
-
重写了class方法,通过class方法直接返回person,不然就返回NSKVONotifying_Person
-
重写了dealloc方法,类释放的时候,做一些清楚释放操作
-
增加了-isKVOA方法,返回YES,则使用KVO
- (void)printMethodNamesOfClass:(Class)cls { unsigned int count; // 获得方法数组 Method *methodList = class_copyMethodList(cls, &count); // 存储方法名 NSMutableString *methodNames = [NSMutableString string]; // 遍历所有的方法 for (int i = 0; i < count; i++) { // 获得方法 Method method = methodList[i]; // 获得方法名 NSString *methodName = NSStringFromSelector(method_getName(method)); // 拼接方法名 [methodNames appendString:methodName]; [methodNames appendString:@", "]; } // 释放 free(methodList); // 打印方法名 NSLog(@"%@ %@", cls, methodNames); } //输出 2019-12-29 21:21:04.068859+0800 Interview01[11719:1127666] NSKVONotifying_Person setAge:, class, dealloc, _isKVOA,
tips:
1.手动调用willChangeValueForKey:和didChangeValueForKey:会触发KVO
2.直接修改成员变量不会触发KVO,通过KVC给属性设置值如果调用了set方法就会触发KVO
网友评论