CC的博客

  • 首页
  • iOS
  • Android
  • React-Native
  • 读书杂谈
  • About
CC
记录美好生活
  1. 首页
  2. 技术编程
  3. iOS
  4. 正文

KVO原理探索

2021/03/23

KVO使用方法

给对象的指定属性绑定监听,并设置监听类型。每当该对象指定的属性被改变时,系统就会调用observeValueForKeyPath方法。

 - (void)viewDidLoad {
    [super viewDidLoad];
    People *people = [People new];
    self.people = people;
    /*
     作用:给对象绑定一个监听器(观察者)
     - Observer 观察者
     - KeyPath 要监听的属性
     - options 选项(方便在监听的方法中拿到属性值)
     */
    [people addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];

    // 移除监听
//    [people removeObserver:self forKeyPath:@"name"];


}
/**
 *  当监听的属性值发生改变
 *
 *  @param keyPath 要改变的属性
 *  @param object  要改变的属性所属的对象
 *  @param change  改变的内容
 *  @param context 上下文
 */
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
    NSLog(@"%@------%@------%@", keyPath, object, change);
}

实现原理

其实,每当我们使用KVO的addObserver时,系统会创建一个派生类NSKVONotifying_People。然后,系统动态地让其继承People类,并添加方法:setName。在NSKVONotifying_People的setName方法中添加observeValueForKeyPath方法的调用。

在KVO的使用中,我们对NSKVONotifying_People是完全不知情的。那么,我们如何调用它的setName呢?其实,在创建NSKVONotifying_People时,我们就将People的isa指针指向,换成了NSKVONotifying_People。

在addObserver方法前后分别打断点查看People的isa指针,如下所示:

可以看到,添加observe之后,people的isa指针变成了NSKVONotifying_People

    self.person1 = [[Person alloc] init];
    self.person1.age = 1;

    self.person2 = [[Person alloc] init];
    self.person2.age = 2;
    NSLog(@"添加监听前person1 class = %s person2 class =  %s",object_getClassName(self.person1),object_getClassName(self.person2));
    // 给person1对象添加KVO监听
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];

    NSLog(@"添加监听后person1 class = %s person2 class =  %s",object_getClassName(self.person1),object_getClassName(self.person2));

输出如下结果:

添加监听前person1 class = Person person2 class =  Person

添加监听后person1 class = NSKVONotifying_Person person2 class =  Person

系统为何动态创建一个NSKVONotifying_Person类?

为了重写setAge方法,做监听赋值,在set方法中通知监听者值发生改变。

查看NSKVONotifying_Person类的setAge:方法,发现是调用了Foundation框架里的_NSSetIntValueAndNotify函数

IMP pp = [self.person1 methodForSelector:@selector(setAge:)];

(IMP) pp = 0x00007fff207bc2b7 (Foundation`_NSSetIntValueAndNotify)
_NSSetIntValueAndNotify函数中做了什么?

1、willChangeValueForKey:

2、真正修改值

3、didChangeValueForKey:(方法内部调用observer的observeValueForKeyPath:ofObject:change:context:方法)

验证--重写Person类的部分方法

- (void)setAge:(int)age
{
    _age = age;
    NSLog(@"setAge");
}

- (void)willChangeValueForKey:(NSString *)key {
    [super willChangeValueForKey:key];
    NSLog(@"willChangeValueForKey");
}

- (void)didChangeValueForKey:(NSString *)key {
    NSLog(@"didChangeValueForKey begin");
    [super didChangeValueForKey:key];
    NSLog(@"didChangeValueForKey end");
}

输出

willChangeValueForKey
setAge
didChangeValueForKey begin
监听到<Person: 0x6000004c8350>的age属性值改变了 - {
    kind = 1;
    new = 21;
    old = 1;
} 
didChangeValueForKey end

NSKVONotifying_Person类有哪些方法?

查看某个类的方法数组

- (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);
}

查看NSKVONotifying_Person和Person类对象的方法列表如下:

NSKVONotifying_Person setAge:, class, dealloc, _isKVOA

Person setAge:, age

发现NSKVONotifying_Person重写了setAge:, class, dealloc, _isKVOA方法

当调用self.person1.age = 10时,相当于给person1实例对象发setAge:的消息,person1实例对象通过isa指针找到NSKVONotifying_Person类对象的方法列表查找到setAge:方法实现也即调用了Foundation内部的_NSSetIntValueAndNotify函数,此函数内部通过superclass指针找到Person类对象调用原本的setAge:方法

self.person1.age = 10;
相当于
[self.person1 setAge:10];
最终转换成runtime的伪代码:
objc_msgSend(self.person1,@selector(setAge:),10)

NSKVONotifying_Person类为何重写class方法?

1、隐藏NSKVONotifying_Person类,对外界调用者无感。

手动触发监听通知?

[self.person1 willChangeValueForKey:@"age"];
sele.person1->_age = 0;
[self.person1 didChangeValueForKey:@"age"];

PS

Foundation框架中共有以下相关的函数:

_NSSetBoolValueAndNotify
_NSSetCharValueAndNotify
_NSSetDoubleValueAndNotify
_NSSetFloatValueAndNotify
_NSSetLongValueAndNotify
_NSSetLongLongValueAndNotify
_NSSetIntValueAndNotify
_NSSetObjectValueAndNotify
_NSSetPointValueAndNotify
_NSSetRangeValueAndNotify
_NSSetRectValueAndNotify
_NSSetShortValueAndNotify
_NSSetSizeValueAndNotify

标签: 暂无
最后更新:2024/07/06

CC

这个人很懒,什么都没留下

点赞
< 上一篇
下一篇 >

COPYRIGHT © 2020 CC的博客. ALL RIGHTS RESERVED.

Theme Kratos

豫ICP备2023032048号