Runtime系列11之-Runtime的应用(四)

KVO的底层原理

  1. 我们知道,KVO是监听一个对象的属性是否发生改变.那么他是怎么监听的呢?
  2. 测试代码如下:

     //1.创建一个Person类
     #import <Foundation/Foundation.h>
     @interface Person : NSObject
     {
         @public;
         int _age;
     }
     //自己已经声明了成员变量,那么这句话就只会生成setter和gettet方法了
     @property (nonatomic, assign) int age;
     @end
     #import "Person.h"
     @implementation Person
     @end
     //2.调用
     #import "ViewController.h"
     #import "Person.h"
        
     @interface ViewController ()
     @property (nonatomic, strong) Person *person;
     @end
        
     @implementation ViewController
     - (void)viewDidLoad {
         [super viewDidLoad];
         Person *p = [[Person alloc] init];
         p.age = 0;
         _person = p;
          NSLog(@"---监听前--");
         //1.监听属性
         [p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
         NSLog(@"--监听后--");
     }
     //2.改变时监听方法
     - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
         NSLog(@"person的值改变了:%d",_person.age);
     }
     //改变属性值
     - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
         //_person->_age = 10;
         _person.age = 10;
     }
     @end
    
    1. 从上面的测试代码,当在Viewcontroller的touchBegan方法中使用 _person.age = 10;时,一点击就回触发observeValueForKeyPath...方法;但是当使用_person->_age = 10;时,点击时不会触发observeValueForKeyPath...方法,因此得出如下结论:
      1. KVO的本质就是监听一个对象有没有调用setter方法
  3. 那么他是如何监听setter方法的调用呢?
    1. 通过在addObserver...方法监听前和监听后打断点看看,Person对象向有什么变化,如下图:
    2. 监听前

    3. 监听后:

  4. 因此,分析出[p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];方法做了如下事:
    1. 用运行时(runtime)自定义一个Person类的子类 NSKVONotifying_Person
    2. 重写NSKVONotifying_Person的setAge方法,在内部恢复父类(Person)的setAge方法[super setAge:age];
    3. 拿到观察者,调用观察者的-observeValueForKeyPath: ofObject: change: context:方法
    4. 如何让外界(父类Person)调用自定义子类NSKVONotifying_Person的setter方法?
      1. 修改当前对象的isa指针,指向NSKVONotifying_Person即可
  5. 代码举例,模拟实现KVO,在这里就不动态创建一个类了,通过系统的方法创建一个Person子类,代码如下:

     //1. 模拟系统的方法:- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;给NSObject添加一个类似方法.
     #import <Foundation/Foundation.h>
     @interface NSObject (KVO)
     - (void)zh_addObserver:(NSObject *_Nullable)observer forKeyPath:(NSString *_Nullable)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
     @end
        
     #import "NSObject+KVO.h"
     #import <objc/runtime.h>
     #import "ZHKVONotifying_Person.h"
     @implementation NSObject (KVO)
     - (void)zh_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
     {
            
         // 1.创建Person子类ZHKVONotifying_Person(用系统方法创建)
         // 2.重写父类的set方法
            
         // 3.修改isa指针,就是把当前对象指向一个新类
         object_setClass(self, [ZHKVONotifying_Person class]);
         // 4.给对象绑定观测者对象
         objc_setAssociatedObject(self, @"observer", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
            
     }
     @end
        
     //2. 子类ZHKVONotifying_Person
     #import <Foundation/Foundation.h>
     #import "Person.h"
     @interface ZHKVONotifying_Person : Person
     @end
        
     #import "ZHKVONotifying_Person.h"
     #import <objc/runtime.h>
     @implementation ZHKVONotifying_Person
     //重写set方法
     - (void)setAge:(int)age
     {
         [super setAge:age];
         // 5.监听到,通知观察者
         // 5.1 获取观察者
         id observer =  objc_getAssociatedObject(self, @"observer");
         // 5.2 调用观察者的方法
         [observer observeValueForKeyPath:@"age" ofObject:observer change:nil context:nil];
     }
     @end
        
     //3. 调用
     #import "ViewController.h"
     #import "Person.h"
     #import "NSObject+KVO.h"
     @interface ViewController ()
     @property (nonatomic, strong) Person *person;
     @end
        
     @implementation ViewController
     - (void)viewDidLoad {
         [super viewDidLoad];
         Person *p = [[Person alloc] init];
         p.age = 0;
         _person = p;
            
         NSLog(@"---监听前--");
         //1.监听属性
          [p zh_addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
     //    [p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
         NSLog(@"--监听后--");
     }
     //2.改变时监听方法
     - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
         NSLog(@"person的值改变了:%d",_person.age);
     }
     //改变属性值
     - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
         _person.age = 10;
     }
     @end
    
Table of Contents