其他分享
首页 > 其他分享> > iOS底层原理 | KVO

iOS底层原理 | KVO

作者:互联网

一、KVO 概述

KVO,(Key-Value Observing),即键值监听,是一种机制,允许注册成为其他对象的观察者,当被观察对象的某个属性值发生改变时,注册的观察者便能获得通知。

二、KVO 基本使用

必须执行以下步骤,才能使对象接收 KVO 兼容属性通知的键值:

  • 将观察者注册到观察对象上 使用这个方法:addObserver:forKeyPath:options:context:
  • 实现 observeValueForKeyPath:ofObject:change:context: 来接收观察者内部值的变化的通知消息。
  • 在观察者从内存释放之前,调用removeObserver:forKeyPath:来移除观察者
- (void)addObserver:(NSObject *)observer
         forKeyPath:(NSString *)keyPath
            options:(NSKeyValueObservingOptions)options
            context:(nullable void *)context
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
- (void)removeObserver:(NSObject *)observer 
            forKeyPath:(NSString *)keyPath;

1. 基本使用

首先有一个 Person 类,只有一个 name 属性,代码如下:

@interface Person : NSObject

@property (nonatomic, copy) NSString *name;

@end

@implementation Person


@end

ViewController.m 中:

@interface ViewController ()

@property (nonatomic, strong) Person *person;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    _person = [Person alloc];
    // 注册 self 也就是 controller 为自己的观察者
    [_person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    _person.name = @"哈哈";
}

// 响应方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"%@ - %@ - %@",keyPath,object,change);
}

// 移除观察者
- (void)dealloc{
    [_person removeObserver:self forKeyPath:@"name"];
}

@end

点击屏幕,控制台输出:

2021-01-18 15:59:15.571749+0800 KVO[3322:245856] name - <Person: 0x6000008c0350> - {
    kind = 1;
    new = "\U54c8\U54c8";
    old = "<null>";
}

NSKeyValueChange 值:

typedef NS_ENUM(NSUInteger, NSKeyValueChange) {
    NSKeyValueChangeSetting = 1,//设值
    NSKeyValueChangeInsertion = 2,//插入
    NSKeyValueChangeRemoval = 3,//移除
    NSKeyValueChangeReplacement = 4,//替换
};

2. 手动触发 observer 回调

我们改变了 name 的值,就自动触发了 observer 回调,但是有时候我们并不一定每一次都通知,比如满足某一个条件的时候,才想着通知一下。
我们先在 Person 中添加一个方法,关闭自动触发:

// 对所有的都关闭
//+ (BOOL)automaticallyNotifiesObserversOfName{
//    return NO;
//}

// 可以对某个指定key 关闭
+(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
    if([key isEqualToString:@"name"]){
        NSLog(@"关闭了自动触发");
        return NO;
    }
    return YES;
}

person.name 赋值的地方添加上手动触发的代码:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    // name 的值即将改变
    [_person willChangeValueForKey:@"name"];
    _person.name = @"哈哈";
    // name的值改变完成
    [_person didChangeValueForKey:@"name"];
}

3. 观察属性.属性的变化

Person 类中有 Dog 属性,Dog 类中有 age 属性,观察 age 的变化如下:

[_person addObserver:self forKeyPath:@"dog.age" options:(NSKeyValueObservingOptionNew) context:nil];

4. 观察多个属性变化

Person 类中再添加两个属性:firstNamelastName,它们两个是 name 的组成,当给 name 添加监听时,当 firstNamelastName 任意一个变化,都要收到通知:

+ (NSSet<NSString *> *)keyPathsForValuesAffectingName{
    NSSet *keyPaths = [NSSet setWithArray:@[@"firstName",@"lastName"]];
    return keyPaths;
}

4. 观察可变数组变化

Person 添加一个可变数组属性,当向这个可变数组添加数据时,是不会调用 setter 方法的,也不会触发 KVO 的回调,代码如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    _person = [Person alloc];
    // sons是一个可变数组
    _person.sons = [@[] mutableCopy];
    // 观察 sons 的变化
    [_person addObserver:self forKeyPath:@"sons" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    // sons添加元素
    [_person.sons addObject:@"lili"];
}

直接通过 [_person.sons addObject:@"lili"]; 无法触发 KVO 回调,针对于可变数组的集合类型,需要通过 mutableArrayValueForKey 方法将元素添加到可变数组中,才能触发 KVO 回调,将添加元素的代码改为如下代码即可:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [[_person mutableArrayValueForKey:@"sons"] addObject:@"lili"];
}

三、KVO 底层探索

KVO 使用 isa-swizzling 技术实现自动键值观察。

  • 该isa指针,指向对象的类,它保持一个调度表。该分派表实质上包含指向该类实现的方法的指针以及其他数据。
  • 当为对象的属性注册观察者时,将修改观察对象的 isa 指针,指向中间类而不是真实类。结果,isa 指针的值不一定反映实例的实际类。
  • 您永远不要依靠 isa 指针来确定类成员。相反,您应该使用该 [class] 方法确定对象实例的类。

参考文档

  1. 《Key-Value Observing Programming Guide》 苹果官方文档

  2. 《[iOS] KVO底层原理》 code_ce

  3. 《iOS底层--KVO(二)-原理》 Engandend

标签:name,Person,KVO,void,iOS,person,context,底层
来源: https://www.cnblogs.com/qiuzhaohai/p/14401442.html