• iOS-KVO的原理


    KVO全称为Key-Value Observing, 即键值监听,用于指定对象属性值的改变。

    问题一、iOS用什么方式实现对一个对象的KVO(KVO的本质是什么?)

    • 利用RuntimeAPI动态生成一个子类,并且让instance对象的isa指向这个全新的子类
    • 当修改instance对象的属性时,会调用Foundation_NSSetXXXValueAndNotify函数,这个函数内部调用的方法如下
    1. willChangeValueForKey:
    2. 父类原来的setter
    3. didChangeValueForKey:
    4. 内部会触发监听器(Oberser)的监听方法( observeValueForKeyPath:ofObject:change:context:

    问题二、如何手动触发KVO

    • 手动调用willChangeValueForKey:didChangeValueForKey:

    问题三、直接修改成员变量会触发KVO么?

    • 不会触发KVO 因为其内部是重写set方法来达到监听的

    上面的问题 下面都会一一作出解释

    定义如下一个Person类:

    @interface Person : NSObject
    @property (assign, nonatomic) int age;
    @end

    给在ViewController.h 里面给Person类的instance对象添加KVO

    #import "ViewController.h"
    #import "MJPerson.h"
    
    @interface ViewController ()
    @property (strong, nonatomic) MJPerson *person1;
    @property (strong, nonatomic) MJPerson *person2;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.person1 = [[MJPerson alloc] init];
        self.person1.age = 1;
        
        self.person2 = [[MJPerson alloc] init];
        self.person2.age = 2;
        
        // 给person1对象添加KVO监听
        NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
        [self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
    //    self.person1.age = 21;
    //    self.person2.age = 22;
        
        // NSKVONotifying_MJPerson是使用Runtime动态创建的一个类,是MJPerson的子类
        
        // self.person1.isa == NSKVONotifying_MJPerson
        [self.person1 setAge:21];
        
        // self.person2.isa = MJPerson
        [self.person2 setAge:22];
    }
    
    - (void)dealloc {
        [self.person1 removeObserver:self forKeyPath:@"age"];
    }
    
    // 当监听对象的属性值发生改变时,就会调用
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
    {
        NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
    }
    
    @end

    当给Person的instance对象添加KVO监听后 内部发生如下的变化

    1.Runtime会动态创建一个名为NSKVONotifying_Person的Person子类。
    2.并将instance对象的isa指针指向这个子类Class。
    3.并重写了属性的set方法 里面的实现如下 

    1. 调用 willChangeValueForKey: 
    2.  [super setAge:age] 
    3. 调用didChangeValueForKey: 里面会去通知监听器,属性值发生了改变

    NSKVONotifying_Person类的setAge:方法的伪代码如下:

    - (void)setAge:(int)age
    {
        //调用Foundation框架的_NSSet***ValueAndNotify方法,与具体参数类型有关
        _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];
    }
    
    

    NSKVONotifying_Person 里面还重写了如下方法: 

    // 屏蔽内部实现,隐藏了NSKVONotifying_Person类的存在
    - (Class)class
    {
        return [Person class];
    }
    
    - (void)dealloc
    {
        // 收尾工作
    }
    
    - (BOOL)_isKVOA
    {
        return YES;
    }

    如何知道里面还有这些方法呢 我们可以用runtime 获取打印下

    #import "ViewController.h"
    #import "MJPerson.h"
    #import <objc/runtime.h>
    
    @interface ViewController ()
    @property (strong, nonatomic) MJPerson *person1;
    @property (strong, nonatomic) MJPerson *person2;
    @end
    
    //@implementation NSObject
    //
    //- (Class)class
    //{
    //    return object_getClass(self);
    //}
    //
    //@end
    
    // 反编译工具 - Hopper
    
    @implementation ViewController
    
    - (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);
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.person1 = [[MJPerson alloc] init];
        self.person1.age = 1;
        
        self.person2 = [[MJPerson alloc] init];
        self.person2.age = 2;
        
        // 给person1对象添加KVO监听
        NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
        [self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];
        
        [self printMethodNamesOfClass:object_getClass(self.person1)];
        [self printMethodNamesOfClass:object_getClass(self.person2)];
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
    //    [self.person1 setAge:21];
        //手动触发KVO 调用willChangeValueForKey 和 didChangeValueForKey
        [self.person1 willChangeValueForKey:@"age"];
        [self.person1 didChangeValueForKey:@"age"];
    }
    
    - (void)dealloc {
        [self.person1 removeObserver:self forKeyPath:@"age"];
    }
    
    // observeValueForKeyPath:ofObject:change:context:
    // 当监听对象的属性值发生改变时,就会调用
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
    {
        NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
    }
    
    @end

    打印如下

    由此可见,直接修改成员变量的值不会触发KVO。

  • 相关阅读:
    排序算法的实现(冒泡,选择,插入 O(N*N)--理解方法实现
    HTTPS工作原理和TCP握手机制
    HTTP协议学习
    IP头,TCP头,UDP头,MAC帧头定义
    单链表的实现
    数字图像处理------中值滤波
    对于矩阵的理解-- by 孟岩老师
    java编码问题总结
    jsp数据库连接大全和数据库操作封装到Javabean
    构建一个高可扩展性javabean和jsp连接数据库操作
  • 原文地址:https://www.cnblogs.com/junhuawang/p/14026841.html
Copyright © 2020-2023  润新知