iOS之KVO实现原理探究

2018-09-07  本文已影响53人  内心戏十足的伪胖子

KVO,就是key-value-observing,键值观察者模式。开发中经常会使用到,并且面试大概率问到其底层实现原理。

用法

eg:

#import <Foundation/Foundation.h>
@interface Programmer : NSObject
@property (copy, nonatomic) NSString *name;
@end

#import "Programmer.h"
@implementation Programmer
- (void)setName:(NSString *)name {
    _name = name;
}
@end

#import "ViewController.h"
#import "Programmer.h"
#import <objc/runtime.h>

@interface ViewController ()

@property (strong, nonatomic) Programmer *programmer1;
@property (strong, nonatomic) Programmer *programmer2;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.programmer1 = [[Programmer alloc] init];
    self.programmer1.name = @"Bob";
    self.programmer2 = [[Programmer alloc] init];
    self.programmer2.name = @"Lili";
    
    NSLog(@"添加监听之前person1的isa指针指向%@",object_getClass(self.programmer1));//在调试模式下可以直接 po self.person1->isa
    NSLog(@"添加监听之前person类对象的isa指针指向%@",object_getClass(object_getClass(self.programmer1)));
    NSLog(@"添加监听之前person1的setName方法的地址%p",[self.programmer1 methodForSelector:@selector(setName:)]);//在调试模式下,通过 p (IMP)地址 打印出这个IMP地址对应的方法名称
    
    [self.programmer1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
    
    NSLog(@"添加监听之后person1的isa指针指向%@",object_getClass(self.programmer1));
    NSLog(@"添加监听之后person类对象的isa指针指向%@",object_getClass(object_getClass(self.programmer1)));
    NSLog(@"添加监听之后person1的setName方法的地址%p",[self.programmer1 methodForSelector:@selector(setName:)]);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.programmer1.name = @"Mark";
    self.programmer2.name = @"Jeny";
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"keyPath===%@",keyPath);
    NSLog(@"object===%@",object);
    NSLog(@"change===%@",change);
    NSLog(@"context===%@",context);
}
  1. 定义一个Programmer类,有一个name的property
  2. 然后在控制器中有一个@property (strong, nonatomic) Programmer *programmer1;
  3. 在ViewDidLoad中添加观察,[self.programmer1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];,NSKeyValueObservingOptionNew和NSKeyValueObservingOptionOld分别表示变化之前的值和改变之后的值
  4. 在监听的控制器中实现- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context这个方法,其中change为一个字典,里面包含新值和旧值

原理

上面的例子中:

  1. Programmer类的实例programmer1name属性被控制器监听了。这时,OC的runtime机制生成了一个KVONotifying_ Programmer的类
  2. programmer1实例的isa指针从指向Programmer的类对象,变成指向KVONotifying_ Programmer的类对象,而KVONotifying_ Programmer的isa指针指的是KVONotifying_ Programmer的meta-class元类对象,KVONotifying_ Programmer的superclass指针指的是Programmer的类对象
  3. 修改programmer1name属性的时候调用了Foundation框架下的一个_NSSetObjectValueAndNotify方法
  4. KVONotifying_ Programmer类重写了Programmer类属性name的setter方法加入了NSObject的两个方法:willChangeValueForKey:(值改变之前)和didChangevlueForKey:(值改变之后)。在一个被观察属性发生改变之前,willChangeValueForKey:一定会被调用,这就会记录旧的值。而当改变发生后,didChangeValueForKey:会被调用,继而observeValueForKey:ofObject:change:context:也会被调用。

探究

待续

如果有错误,感谢各位大佬指正。

上一篇下一篇

猜你喜欢

热点阅读