iOS底层原理 | KVO
作者:互联网
一、KVO 概述
KVO,(Key-Value Observing),即键值监听,是一种机制,允许注册成为其他对象的观察者,当被观察对象的某个属性值发生改变时,注册的观察者便能获得通知。
二、KVO 基本使用
必须执行以下步骤,才能使对象接收
KVO
兼容属性通知的键值:
- 将观察者注册到观察对象上 使用这个方法:addObserver:forKeyPath:options:context:
- 实现 observeValueForKeyPath:ofObject:change:context: 来接收观察者内部值的变化的通知消息。
- 在观察者从内存释放之前,调用removeObserver:forKeyPath:来移除观察者
- 1. 注册观察者
- (void)addObserver:(NSObject *)observer
forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(nullable void *)context
- 2. 注册观察者
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
- 3. 注销观察者
- (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
类中再添加两个属性:firstName
和 lastName
,它们两个是 name
的组成,当给 name
添加监听时,当 firstName
和 lastName
任意一个变化,都要收到通知:
- 在
Person
类中加入类方法+ keyPathsForValuesAffectingValueForKey
,返回一个容器。 - 注册观察
Person
的name
属性,这样就可以在firstName
和lastName
变化时,也收到相应的回调。
+ (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]
方法确定对象实例的类。
参考文档
标签:name,Person,KVO,void,iOS,person,context,底层 来源: https://www.cnblogs.com/qiuzhaohai/p/14401442.html