iOS KVC & KVO

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:的原理

image.png

valueForKey:的原理

image.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 *)change context:(nullable void *)context;
实现原理
image.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

你可能感兴趣的:(iOS KVC & KVO)