iOS-KVO 实现原理

KVO 属性观察者 是观察者模式的一种具体实现,主要通过观察对象属性值的变化,触发对应的方法

下面学习下 KVO 的具体实现 和 底层实现原理


原生观察者实现

1、为 狗 对象 d 添加一个观察者为 自己, 同时监听对象 d->age 年龄 属性的改变

Dog *d = [Dog new];

[p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];

2、实现观察者监听方法,实时监听 d->age 年龄 属性的改变,并获取改变后的 new

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"%@-%@", keyPath,change[@"new"]);
}

3、在对象销毁前,移除狗对象的观察者

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

系统 KVO 的底层实现原理
  • KVO 是基于 runtime 机制实现的
  • 首先是给 NSObject 添加一个分类 (Category), 添加两个方法:
    • 方法一:为自己添加观察者(observer)
    • 方法二:属性变化后执行的动作(action)
  • 为这个类的对象添加观察者(方法一)和实现属性变化后的动作方法(方法二)
  • 当某个类的对象第一次被观察时(方法一),系统就会在运行时动态地创建该类的一个派生类
  • 在这个类中实现添加观察者方法(方法一),并将观察者对象和自己关联起来,并将指针指向生成的派生类
  • 在这个派生类中重写基类中被观察属性的 setter 方法,并获取对象的关联对象
  • 然后在 setter 方法中,执行 关联对象 的方法二,触发对象变化后的 action 动作,这样属性变化后的方法将会执行,达到监听的作用

具体代码实现
一、给 NSObject 添加一个分类 (Category), 添加两个方法,并空实现
@interface NSObject (KVO)

- (void)xm_addObserver:( NSObject * _Nonnull )observer forKeyPath:(NSString * _Nonnull)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;

- (void)xm_observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;

@end
@implementation NSObject (KVO)

#pragma mark - 空实现
- (void)xm_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {

}

- (void)xm_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {

}

@end

二、为某个类的对象添加观察者(方法一)和实现属性变化后的动作方法(方法二)
Dog *d = [Dog new];
[d xm_addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
- (void)xm_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"%@------%@", keyPath, change[@"new"]);
}

三、当某个类的对象第一次被观察时(方法一),在运行时动态地创建该类的一个派生类
@interface KV0_Dog : Dog

@end

四、在这个类中实现添加观察者方法(方法一),并将观察者对象和自己关联起来,并将指针指向生成的派生类
#import "Dog.h"
#import <objc/message.h>

@implementation Dog

- (void)xm_addObserver:( NSObject * _Nonnull )observer forKeyPath:(NSString * _Nonnull)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context {
    // 修改对象的 ias 指针
    object_setClass(self, NSClassFromString(@"KV0_Dog"));

    // 关联对象,将观察者对象和自己关联起来
    objc_setAssociatedObject(self, "observer", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

五、在这个派生类中重写基类中被观察属性的 setter 方法,并获取对象的关联对象,执行关联对象 的方法二,触发对象变化后的 action

#import "KV0_Dog.h"
#import <objc/message.h>
#import "NSObject+KVO.h"

@implementation KV0_Dog

- (void)setAge:(NSInteger)age {
    // 重写父类的属性 setter 方法
    [super setAge:age];

    // 获取自己(父类)的关联对象,观察者对象
    id observer = objc_getAssociatedObject(self, "observer");

    // 触发观察者对象的 observeValueForKeyPath 方法, 并传递参数
    [observer xm_observeValueForKeyPath:@"age" ofObject:self change:@{@"kind": @(1), @"new": @(age)} context:nil];
}

@end

六、改变 Dogage 属性,然后 步骤二的属性观察方法将会被执行,同时属性改变的新值也传递过来了

打印结果

age------1
age------2
age------3
age------4
...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值