添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
  • - (void)setValue:(id)value forKeyPath:(NSString *)keyPath; // 通过KeyPath来设值
  • - (void)setValue:(id)value forKey:(NSString *)key; // 通过Key来设值
  • - (id)valueForKeyPath:(NSString *)keyPath; // 通过KeyPath来取值
  • - (id)valueForKey:(NSString *)key; // 直接通过Key来取值
  • key:可以认为是一个相对路劲

    keyPath:可以认为是一个绝对路劲

    3. 原理

  • setValue:forKey: 原理
  • valueForKey: 原理
  • + (BOOL)accessInstanceVariablesDirectly;
  • 在 setKey和_setKey 都不存在的情况下会查看此方法。 默认返回 YES

    返回 NO,则抛出异常 NSUnknownKwyException

    返回 YES,则可以查找成员变量 _key key _isKey isKey ,若成员变量也找不到,抛出异常 NSUnknownKwyException

    1. 什么是 KVO

    KVO:Key-Value Observing

    功能:“键值监听”,可以用于监听某个对象属性值

    2. 使用

    KVO 使用非常简单,主要为以下几步

  • 为需要监听的属性添加观察者
  • 添加观察者方法
  • 移除观察者
  • @interface MJPerson : NSObject
    @property (assign, nonatomic) int age;
    - (void)test;
    @implementation MJPerson
    - (void)test {}
    
    @interface ViewController ()
    @property (strong, nonatomic) MJPerson *person1;
    @property (strong, nonatomic) MJPerson *person2;
    @property (nonatomic, assign) int number;
    @implementation ViewController
    - (void)viewDidLoad {
      [super viewDidLoad];
      self.number = 0;
      self.person1 = [[MJPerson alloc] init];
      self.person1.age = 1;
      self.person2 = [[MJPerson alloc] init];
      self.person2.age = 2;
      // 给person对象添加KVO监听
      NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
      [self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];
      [self addObserver:self forKeyPath:@"number" options:options context:nil];
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
      // 值改变
      self.person1.age = 10; // 会触发 KVO
      self.person1->_age = 10; // 不会触发 KVO
      self.person2.age = 20;
      _number = 20; // 不会触发 KVO
      self.number = 20; // 会触发 KVO
      [self setNumber:20]; // 会触发 KVO
    // 移除观察者
    - (void)dealloc {
      [self.person1 removeObserver:self forKeyPath:@"age"];
      [self removeObserver:self forKeyPath:@"number"];
    // 当监听对象的属性值发生改变时,就会调用
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
      NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
    

    3. 本质 - 重写 set 方法

    Automatic key-value observing is implemented using a technique called isa-swizzling… When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class

    利用 RuntimeAPI 动态生成一个子类 NSKVONotifying_MJPerson,并且让 instance 对象的 isa 指向这个全新的子类

    当修改 instance 对象的属性是,会调用 _NSSet***ValueAndNotify()函数

    willChangeValueForKey:

    父类原来的 setter

    didChangeValueForKey:

  • 内部会触发监听器(Observer)的监听方法(observeValueForKeyPath:* ofObject:* change:* context:*)
  • 观察者观察的是属性,只有遵循 KVO 变更属性值的方式才会执行 KVO 的回调方法。如: self.number = 20; [self setNumber:20]; 如果赋值没有通过 setter 方法或者 KVC,而是直接修改属性对应的成员变量,是不会触发 KVO 机制,更加不会调用回调方法。如: _number = 20; , self.person1->_age = 10; (可进行 手动触发 ) 所以使用 KVO 机制的前提是遵循 KVO 的属性设置方式来变更属性值。

    4. 验证

    通过断点调试发现

    self.perosn1.isa == NSKVONotifying_MJPerson // 类对象
    self.perosn2.isa == MJPerson // 类对象
    NSLog(@"添加之前的 class 对象:%@", object_getClass(self.person1));
    NSLog(@"添加之前的 class 对象:%@", object_getClass(self.person2));
    // 给person1对象添加KVO监听
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];
    NSLog(@"添加之后的 class 对象:%@", object_getClass(self.person1));
    NSLog(@"添加之后的 class 对象:%@", object_getClass(self.person2));
    

    证明 NSKVONotifying_MJPerson 中的方法(重写的方法)

    - (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);
    [self printMethodNamesOfClass:object_getClass(self.person1)];
    [self printMethodNamesOfClass:object_getClass(self.person2)];
    

    重写 setAge, class, dealloc, _isKVOA

    @interface NSKVONotifying_MJPerson : MJPerson
    @implementation NSKVONotifying_MJPerson
    - (void)setAge:(int)age {
      _NSSetIntValueAndNotify();
    // 伪代码
    void _NSSetIntValueAndNotify() {
      [self willChangeValueForKey:@"age"];
      [super setAge:age];
      [self didChangeValueForKey:@"age"];
    - (void)didChangeValueForKey:(NSString *)key {
      // 通知监听器,某某属性值发生了改变
      [observer observeValueForKeyPath:key ofObject:self change:nil context:nil];
    

    5. 手动触发

  • 如果需要手动触发 KVO, 则需要调用 willChangeValueForKey:didChangeValueForKey:
  • [self.person1 willChangeValueForKey:@"age"];
    self.person1.->_age = 20;
    [self.person1 didChangeValueForKey:@"age"];
    

    个人学习记录。

    好记性不如烂笔头,有关内容来自小马哥学习视频。

    Brick Mover @ 杭州
    私信