iOS进阶

03-OC中KVO的底层原理

2020-06-14  本文已影响0人  光强_上海

KVO本质

什么是KVO?

KVO:Key-Value Observing,用来监听类的某个对象的某个属性的值发生变化,简称键值观察

我们先来看下KVO的基本用法:

// 创建一个Person类
@interface Person : NSObject

@property (nonatomic, assign) int age;
@end


@implementation Person

- (void)setAge:(int)age {
    _age = age;
    NSLog(@"setAge:");
}

- (void)willChangeValueForKey:(NSString *)key {
    [super willChangeValueForKey:key];
    NSLog(@"willChangeValueForKey:");
}

- (void)didChangeValueForKey:(NSString *)key {
    NSLog(@"didChangeValueForKey:begin");
    [super didChangeValueForKey:key];
    NSLog(@"didChangeValueForKey:end");
}
@end


#import "ViewController.h"
#import "Person.h"

@interface ViewController ()

@property (nonatomic, strong) Person *person1;
@property (nonatomic, strong) Person *person2;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.person1 = [[Person alloc] init];
    self.person2 = [[Person alloc] init];
    self.person1.age = 10;
    self.person2.age = 11;
    
    [self.person1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"111"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"keyPath = %@ object = %@ change = %@ context = %@",keyPath, object, change, context);
    /**
     keyPath = age object = <Person: 0x600001ec4810> change = {
         kind = 1;
         new = 20;
         old = 10;
     } context = 111
     */
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//    self.person1.age = 20;
//    self.person2.age = 22;
    [self.person1 setAge:20];
    [self.person2 setAge:22];
}

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

我们创建了一个Person类,并且给Person类添加了一个Int age属性,注意:我们在控制器ViewController中监听了preson1实例对象的age属性值的变化,并没有监听person2实例对象的age属性值的变化

接下来我们打印下person1person2实例对象的isa指针,打印结果如下:

(lldb) p self.person1->isa
(Class) $0 = NSKVONotifying_Person
(lldb) p self.person2->isa
(Class) $1 = Person

这时我们就发现,person1实例对象添加了age属性的监听,其isa指向NSKVONotifying_Personperson2实例对象没有添加age属性监听,其isa指向Person

我们从上篇章节中知道一个结论:实例对象的isa指向的是类对象

也就是说person1实例对象的类对象是NSKVONotifying_Personperson2实例对象的类对象是Person

NSKVONotifying_Person

这个类是runtime运行时动态为我们创建的一个全新的类,它是Person类的子类

实例对象未使用KVO监听的内存结构图如下:

image

实例对象使用了KVO监听的内存结构图如下:

image

我们创建一个NSKVONotifying_Person类的伪代码如下:

@interface NSKVONotifying_Person : NSObject

@end

@implementation NSKVONotifying_Person

// 重新setAge:函数
- (void)setAge:(int)age {
    // 这`setAge:`方法中调用了`_NSSetIntValueAndNotify()`这个C语言函数
    _NSSetIntValueAndNotify();
}

// _NSSetIntValueAndNotify() 函数的伪代码实现
void _NSSetIntValueAndNotify() {

     // 1. 调用willChangeValueForKey:
    [self willChangeValueForKey:@"age"];
    
    // 2.调用父类的`setAge:`函数
    [super setAge:age];
    
    // 3. 调用didChangeValueForKey:
    [self didChangeValueForKey:@"age"];
}

// didChangeValueForKey: 函数的伪代码实现
- (void)didChangeValueForKey:(NSString *)key {
    // 调用监听器的监听方法,通知监听器某一个对象的属性值发生了改变
    [observer observeValueForKeyPath:key ofObject:self change:nil context:nil];
}

// 实现`class`方法,用来屏蔽内部实现,苹果不希望开发者知道`NSKVONotifying_Person`这个类的存在
- (Class)class {
    return self;
}

- dealloc {
    // 收尾工作
}

- (BOOL)_isKVOA {
    return YES;
}
@end

通过NSKVONotifying_Person类中的伪代码实现,我们可以知道KVO底层的实现逻辑

我们都知道当我们设置了一个对象属性的KVO,当这个属性的值发生变化,就会自动触发KVO监听,那么我们如何实现手动来触发一个KVO?

手动触发一个对象的KVO很简单,我们只需要调用下面两个方法即可:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//    self.person1.age = 20;
//    self.person2.age = 22;
//    [self.person1 setAge:20];
//    [self.person2 setAge:22];
    
    // 手动触发KVO
    [self.person1 willChangeValueForKey:@"age"];
    [self.person1 didChangeValueForKey:@"age"];
}

那么修改成员变量的值会触发KVO吗?,怎么实现修改成员变量的值也能触发KVO?

直接修改成员变量的值是不能触发KVO的,因为成员变量没有对应的set:方法,KVO的本质就是重写属性的set:方法

要想实现修改成员变量的值,也能触发KVO,我们可以使用手动触发KVO如下:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//    self.person1.age = 20;
//    self.person2.age = 22;
//    [self.person1 setAge:20];
//    [self.person2 setAge:22];
    
    [self.person1 willChangeValueForKey:@"age"];
    self.person1 -> _age = 33;
    [self.person1 didChangeValueForKey:@"age"];
}

练习Demo地址:https://github.com/guangqiang-liu/03-KVODemo

更多文章

上一篇下一篇

猜你喜欢

热点阅读