KVC和KVO

声明:本文仅供记忆使用,并不适合新手小白观看

KVC

  • KVC的全称是Key-Value Coding,俗称“键值编码”,可以通过一个key来访问某个属性
  • 常见的API有
1
2
3
4
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
- (void)setValue:(id)value forKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (id)valueForKey:(NSString *)key;
  • setValue:forKey:原理
  • valueForKey:原理

KVO

  • KVO的全称是Key-Value Observing,俗称“键值监听”,可以用于监听某个对象属性值的改变,主要通过isa-swizzling技术实现的

  • 使用API

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 注册观察者
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [self.person addObserver:self forKeyPath:@"name" options:options context:@"1111"];

    // 当监听对象的属性值发生改变时,就会调用
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
    }

    - (void)dealloc {
    // 移除监听
    [self.person removeObserver:self forKeyPath:@"name"];
    }
  • 实现原理

    1. runtime新生成一个NSKVONotifying_xxx类对象继承原来的类对象,对象的isa指针指向它;
    2. 重写NSKVONotifying_xxxset和class方法;
    3. NSKVONotifying_xxx的set方法IMP指向Foundation框架_NSSetxxxValueAndNotify的实现;
    4. _NSSetxxxValueAndNotify的内部实现为:
      1
      2
      3
      [self willChangeValueForKey:@"age"];
      [super setAge:age];
      [self didChangeValueForKey:@"age"];
    5. didChangeValueForKey:内部会调用observerobserveValueForKeyPath:ofObject:change:context:方法
  • 手动触发kvo

    1
    2
    [self.person willChangeValueForKey:@"age"];
    [self.person didChangeValueForKey:@"age"];
  • 使用KVC会触发KVO,因为KVC内部会主动调用willChangeValueForKey:didChangeValueForKey:

KVO注意事项

  • 崩溃原因总结
    1. 添加监听后没有清除会导致闪退
    2. 清除不存在的key也会闪退
    3. 添加重复的key导致闪退
    4. observe忘记写监听回调方法 observeValueForKeyPath
    5. 监听者和被监听者的生命周期不同
  • 自己解决,利用 @try @catch
    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
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    #import "NSObject+MKVO.h"
    #import <objc/runtime.h>

    @implementation NSObject (MKVO)

    + (void)load{
    [self switchMethod];
    }
    + (void)switchMethod{

    //移除kvo的方法
    SEL removeSel = @selector(removeObserver:forKeyPath:);
    SEL myRemoveSel = @selector(removeDasen:forKeyPath:);

    //监听的方法
    SEL addSel = @selector(addObserver:forKeyPath:options:context:);
    SEL myaddSel = @selector(addDasen:forKeyPath:options:context:);


    Method systemRemoveMethod = class_getClassMethod([self class],removeSel);
    Method DasenRemoveMethod = class_getClassMethod([self class], myRemoveSel);

    Method systemAddMethod = class_getClassMethod([self class],addSel);
    Method DasenAddMethod = class_getClassMethod([self class], myaddSel);

    //交换方法的实现
    method_exchangeImplementations(systemRemoveMethod, DasenRemoveMethod);
    method_exchangeImplementations(systemAddMethod, DasenAddMethod);
    }

    //利用@try @catch
    // 交换后的方法
    - (void)removeDasen:(NSObject *)observer forKeyPath:(NSString *)keyPath{
    @try {//相对应解决方法1而已,只是把@try @catch 写在这里而已
    [self removeDasen:observer forKeyPath:keyPath];
    } @catch (NSException *exception) {

    }
    }

    // 交换后的方法
    - (void)addDasen:(NSObject *)observer forKeyPath:(NSString *)keyPath options:
    (NSKeyValueObservingOptions)options context:(void *)context{
    [self addDasen:observer forKeyPath:keyPath options:options context:context];
    }
  • 使用JJException解决

参考文章