美文网首页iOS 技能
iOS KVO底层原理&&KVO的isa指向(一)

iOS KVO底层原理&&KVO的isa指向(一)

作者: 枫紫_6174 | 来源:发表于2020-09-24 17:05 被阅读0次

    一.简单复习一下KVO的使用

    • 定义一个类,继承自NSObject,并添加一个name的属性
    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface TCPerson : NSObject
    
    @property (nonatomic, copy) NSString *name;
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    
    • 在ViewController我们简单的使用一下KVO
    #import "ViewController.h"
    #import "TCPerson.h"
    @interface ViewController ()
    @property (nonatomic, strong) TCPerson *person1;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.person1 = [[TCPerson alloc]init];
        self.person1.name = @"liu yi fei";
        [self.person1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
    }
    
    /// 点击屏幕出发改变self.person1的name
    /// @param touches touches description
    /// @param event event description
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        self.person1.name = @"cang lao shi";
    }
    
    /// 监听回调
    /// @param keyPath 监听的属性名字
    /// @param object 被监听的对象
    /// @param change 改变的新/旧值
    /// @param context context description
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
        NSLog(@"监听到%@对象的%@发生了改变%@",object,keyPath,change);
    }
    
    /// 移除观察者
    - (void)dealloc{
        [self.person1 removeObserver:self forKeyPath:@"name"];
    }
    @end
    

    当点击屏幕的时候,控制台输出:

    2020-09-24 15:53:52.527734+0800 KVO_TC[9255:98204] 监听到<TCPerson: 0x600003444d10>对象的name发生了改变{
        kind = 1;
        new = "cang lao shi";
        old = "liu yi fei";
    }
    

    二.深入剖析KVO的底层

    • 在- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
      self.person1.name = @"cang lao shi";
      }我们知道self.person1.name的本质是[self.person1 setName:@"cang lao shi"];
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    //    self.person1.name = @"cang lao shi";
        [self.person1 setName:@"cang lao shi"];
    }
    

    在TCPerson的.m文件,我们从写setter方法并打断点,可以看到当我们点击屏幕的时候,我们发现进入了setter方法:

    - (void)setName:(NSString *)name{
        _name = name;
    }
    
    • 在ViewController我们新建一个person2,代码变成了:
    #import "ViewController.h"
    #import "TCPerson.h"
    @interface ViewController ()
    @property (nonatomic, strong) TCPerson *person1;
    @property (nonatomic, strong) TCPerson *person2;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.person1 = [[TCPerson alloc]init];
        self.person1.name = @"liu yi fei";
        [self.person1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
        
        
        self.person2 = [[TCPerson alloc] init];
        self.person2.name = @"yyyyyyyy";
    }
    
    /// 点击屏幕出发改变self.person1的name
    /// @param touches touches description
    /// @param event event description
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        self.person1.name = @"cang lao shi";
    //    [self.person1 setName:@"cang lao shi"];
        
        self.person2.name = @"ttttttttt";
    }
    
    /// 监听回调
    /// @param keyPath 监听的属性名字
    /// @param object 被监听的对象
    /// @param change 改变的新/旧值
    /// @param context context description
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
        NSLog(@"监听到%@对象的%@发生了改变%@",object,keyPath,change);
    }
    
    /// 移除观察者
    - (void)dealloc{
        [self.person1 removeObserver:self forKeyPath:@"name"];
    }
    @end
    
    • 注意:当我们点击屏幕的时候输出的结果是:
    2020-09-24 16:10:36.750153+0800 KVO_TC[9313:105906] 监听到<TCPerson: 0x600002ce8230>对象的name发生了改变{
        kind = 1;
        new = "cang lao shi";
        old = "liu yi fei";
    }
    
    
    • 既然我们改变name的值的时候走的都是setName:setter方法,按理说观察属性变化的时候,person2的值也应该被观察到,为什么它不会观察到person2?

    三.KVO的isa指向

    • 上篇文章中我分析了实例对象,类对象,元类对象的isa,既然当我们改变属性值的时候,其本质是调用setter方法,那么在KVO中,person1和person2的setName方法应该存储在类对象中,我们先来看看这两个实例对象的isa指向:
      打开lldb
    (lldb) p self.person1.isa
    (Class) $0 = NSKVONotifying_TCPerson
      Fix-it applied, fixed expression was: 
        self.person1->isa
    (lldb) p self.person2.isa
    (Class) $1 = TCPerson
      Fix-it applied, fixed expression was: 
        self.person2->isa
    (lldb) 
    
    • 从上面的打印我们看到 self.person1的isa指向了NSKVONotifying_TCPerson,而没有添加观察着的self.person2的isa却指向的是TCPerson
    • NSKVONotifying_TCPerson是runtime动态创建的类,继承自TCPerson,其内部实现可以看成(模拟的NSKVONotifying_TCPerson流程,下面代码不能在xcode中运行):
    #import "NSKVONotifying_TCPerson.h"
    
    @implementation NSKVONotifying_TCPerson
    //NSKVONotifying_TCPerson的set方法实现,其本质来自于foundation框架
    - (void)setName:(NSString *)name{
        _NSSetIntVaueAndNotify();
    }
    //改变过程
    void _NSSetIntVaueAndNotify(){
        [self willChangeValueForKey:@"name"];
        [super setName:name];
        [self didChangeValueForKey:@"name"];
    }
    //通知观察者
    - (void)didChangeValueForKey:(NSString *key){
        [observe  observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context];
    }
    @end
    
    • 未添加观察self.person2实例对象的isa指向流程图:


      elf.person2实例对象的isa指向流程图
    • 添加观察self.person1实例对象的isa指向流程图:


      self.person1实例对象的isa指向流程图
    • 所以KVO其本质是动态生成一个NSKVONotifying_TCPerson类,继承自TCPerson,当实例对象添加观察着之后,实例对象的isa指向了这个动态创建的类,当其属性发生改变时,调用的是该类的setter方法,而不是父类的类对象中的setter方法

    相关文章

      网友评论

        本文标题:iOS KVO底层原理&&KVO的isa指向(一)

        本文链接:https://www.haomeiwen.com/subject/htzzyktx.html