OC底层原理

OC底层原理(三):KVO

2020-12-25  本文已影响0人  跳跳跳跳跳跳跳

KVO,全称为Key-Value Observing,可以用于监听某个类的属性值的改变

KVO的使用

我们创建一个iOS项目,然后新建一个ZJPerson类

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

然后在ViewController这个类中申明ZJPerson属性,在viewDidLoad中给ZJPerson添加KVO

@interface ViewController ()
@property (nonatomic, strong) ZJPerson *person;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    _person = [[ZJPerson alloc]init];
    _person.age = 10;
    
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [_person addObserver:self forKeyPath:@"age" options:options context:nil];
}

- (void)dealloc {
    [_person removeObserver:self forKeyPath:@"age"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    NSLog(@"%@", change);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    _person.age = 20;
}


@end

运行代码后,点击模拟器屏幕,输出如下


截屏2020-12-21 21.44.46.png

KVO的底层实现原理

我们先修改下原来的代码,再初始化一个ZJPerson对象

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    _person1 = [[ZJPerson alloc]init];
    _person1.age = 11;
    
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [_person1 addObserver:self forKeyPath:@"age" options:options context:nil];
    
    _person2 = [[ZJPerson alloc]init];
    _person2.age = 12;
}

- (void)dealloc {
    [_person1 removeObserver:self forKeyPath:@"age"];
    [_person2 removeObserver:self forKeyPath:@"age"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    NSLog(@"%@", change);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    _person1.age = 21;
    _person2.age = 22;
}
@end

运行起来之后,点击屏幕输出如下


截屏2020-12-22 21.19.56.png

可以看到只有person1这个对象触发了KVO的回调,为啥会这样呢?
我们打印两个person对象的isa看下区别

isa.png

可以看到添加了监听的person1对象的isa指向的是NSKVONotifying_ZJPerson这个类对象,而没有添加的监听的person2对象的isa指向的是ZJPerson类对象
那么NSKVONotifying_ZJPerson这个又是什么东西呢?

NSKVONotifying_ZJPerson是runtime动态创建出来的类,是ZJPerson的子类
person2.png
person1.png

NSKVONotifying_ZJPerson内部的结构伪代码如下

@implementation NSKVONotifying_ZJPerson

- (void)setAge:(int)age {
    _NSSetIntValueAndNotify();
    
}

void _NSSetIntValueAndNotify() {
    [self willChangeValueForKey:@"age"];
    [super setAge:age];
    [self didChangeValueForKey:@"age"];
}

- (void)didChangeValueForKey:(NSString *)key {
    [self observeValueForKeyPath:@"age" ofObject:self change:nil context:nil];
}

@end

我们通过代码来验证下
在viewDidLoad中更新如下代码

- (void)viewDidLoad {
    [super viewDidLoad];
    _person1 = [[ZJPerson alloc]init];
    _person1.age = 11;
    _person2 = [[ZJPerson alloc]init];
    _person2.age = 12;
    
    NSLog(@"before ---- person1 class is: %@", object_getClass(_person1));
    NSLog(@"before ----person2 class is: %@", object_getClass(_person2));
    
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [_person1 addObserver:self forKeyPath:@"age" options:options context:nil];
    
    NSLog(@"after ----person1 class is: %@", object_getClass(_person1));
    NSLog(@"after ----person2 class is: %@", object_getClass(_person2));
}

输出如下


截屏2020-12-24 20.17.35.png

可以看到没有添加的_person2指向的是ZJPerson类对象,添加了KVO的_person1对象,isa确实指向了NSKVONotifying_ZJPerson类对象。
我们继续更新代码

- (void)viewDidLoad {
    [super viewDidLoad];
    _person1 = [[ZJPerson alloc]init];
    _person1.age = 11;
    _person2 = [[ZJPerson alloc]init];
    _person2.age = 12;
    
//    NSLog(@"before ---- person1 class is: %@", object_getClass(_person1));
//    NSLog(@"before ----person2 class is: %@", object_getClass(_person2));
    NSLog(@"before ---- person1 method is: %p", [_person1 methodForSelector:@selector(setAge:)]);
    NSLog(@"before ---- person2 method is: %p", [_person2 methodForSelector:@selector(setAge:)]);
    
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [_person1 addObserver:self forKeyPath:@"age" options:options context:nil];
    
    NSLog(@"after ---- person1 method is: %p", [_person1 methodForSelector:@selector(setAge:)]);
    NSLog(@"after ---- person2 method is: %p", [_person2 methodForSelector:@selector(setAge:)]);
    
//    NSLog(@"after ----person1 class is: %@", object_getClass(_person1));
//    NSLog(@"after ----person2 class is: %@", object_getClass(_person2));
}

运行后输出如下


截屏2020-12-24 20.34.41.png

可以看到,在添加KVO之前,person1和person2的方法是相同的,再添加KVO之后,person1的方法就变了,我们打个断点调试,看看这个方法是什么,点击屏幕触发断点,在lldb中调试


截屏2020-12-24 20.37.58.png
打印出的方法也确实上面所提到的方法

面试题

  1. KVO的本质是什么?
  1. 如何手动触发KVO?
    调用如下方法

    • willChangeValueForKey:
    • didChangeValueForKey:
  2. 直接修改成员变量会触发KVO吗?
    不会

上一篇下一篇

猜你喜欢

热点阅读