iOS设计模式 —— KV0

 

刨根问底KVO

KVO 全称 Key-Value Observing。中文叫键值观察。KVO其实是一种观察者模式,观察者在键值改变时会得到通知,利用它可以很容易实现视图组件和数据模型的分离,当数据模型的属性值改变之后作为监听器的视图组件就会被激发,激发时就会回调监听器自身。相比Notification,KVO更加的简单直接。

KVO的操作方法由NSKeyValueCoding提供,而他是NSObject的类别,也就是说ObjC中几乎所有的对象都支持KVO操作。

KVO的使用也很简单,就是简单的3步。

  1. 注册需要观察的对象的属性addObserver:forKeyPath:options:context:
  2. 实现observeValueForKeyPath:ofObject:change:context:方法,这个方法当观察的属性变化时会自动调用.在这个方法中还通过NSKeyValueObservingOptionNew这个参数要求把新值在dictionary中传递过来。
  3. 取消注册观察removeObserver:forKeyPath:context:

我们观察下代码实现,探究实现原理:

Person.h

1
2
3
4
5
6
7
8
9
#import <Foundation/Foundation.h>

@interface Person : NSObject

- (void)registerObserver;

@property (nonatomic, assign) NSInteger age;

@end

Person.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#import "Person.h"

@implementation Person

- (void)registerObserver {

[self addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
}

- (NSString *)description {
return [NSString stringWithFormat:@"%@,%ld",[self valueForKey:@"isa"],self.age];

}

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

if ([keyPath isEqualToString:@"age"]) {
NSLog(@"%@",change);
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}

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

main函数

1
2
3
4
5
6
7
8
Person * p = [[Person alloc] init];
p.age = 20;
NSLog(@"%@",p);

[p registerObserver];

p.age = 30;
NSLog(@"%@",p);

运行程序打印出的log日志为:

1
2
3
4
5
6
7
Person,20
{
kind = 1;
new = 30;
old = 20;
}
NSKVONotifying_Person,30

不要太惊讶,我们慢慢解释。

我重写的Person类的description方法,使用KVC拿到isa指针,获取到self的类名,我们发现在使用KVO之前类名是Person,但注册了KVO之后,类名变成了NSKVONotifying_Person

主要是因为KVO的实现使用了isa-swizzling。在程序运行时Person会生成一个派生类NSKVONotifying_Person,在这个派生类中重写基类中任何被观察属性的setter方法,用来欺骗系统顶替原先的类。在setter方法中实现真正的通知机制.

1
2
3
4
5
6
7
//可以到伪代码

- (void)setAge:(int)age
{
[super setAge:age];
[监听器 observeValueForKeyPath:@"age" ofObject:self change:@{} context:nil];
}

我们又可以猜测,使用KVO,内部一定执行setter方法。

当我们把上面代码 p.age = 30;改成p->_age = 30;

你会发现KVO的方法不走了,也证实了这点。

如果p不提供setAge,getAge方法,还想用KVO.

1
2
3
[p willChangeValueForKey:@"age"];
p->_age = 30;
[p didChangeValueForKey:@"age"];

苹果官方KVO文档:

https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html

另外.....

我的愿望是.......

世界和平.........

原文地址:https://www.cnblogs.com/ioshe/p/5937942.html