发布于 

KVO 梳理

本篇是对 KVO(Key-Value Observing) 的梳理。内容结构:KVO 基本使用、KVO 实现原理、自己实现 KVO。

KVO 基本使用

主要的几个方法:

  • **-addObserver:forKeyPath:options:context:**:注册观察者,开始监听。
  • **-observeValueForKeyPath:ofObject:change:context:**:回调,按需求添加业务代码。
  • **-removeObserver:forKeyPath:**:移除观察者。

实例代码:

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
27
28
29
- (void)viewDidLoad {
[super viewDidLoad];

self.person = [[Person alloc]init];
self.person.age = 15;

// 注册监听
[self.person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
// 值变更
self.person.age = 20;
// 值变更
self.person.age = 30;
}

// 监听回调
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSString *,id> *)change
context:(void *)context {
NSLog(@"change = %@, keyPath =%@, object =%@, context=%@", change, keyPath, object, context);
NSLog(@"属性新值为:%@",change[NSKeyValueChangeNewKey]);
NSLog(@"属性旧值为:%@",change[NSKeyValueChangeOldKey]);
}


- (void)dealloc {
// 移除观察
[self.person removeObserver:self forKeyPath:@"age"];
}

KVO 实现原理

KVO 是 Objective-C 中对观察者模式的实现。KVO 的实现依赖于 Objective-C 的 Runtime 机制。当某个类的对象第一次被观察时,系统就会在 runtime 动态创建该类的一个派生类,在这个派生类中重写原类中任何被观察对象的 setter 方法。派生类在被重写的 setter 方法内实现真正的通知机制。

如现有一 Person 类,它对于的派生类会命名为:**NSKVONotifying_Person**。每个类对象的 isa 指针都指向它所属的类,在一个类对象第一次被观察时,系统会将 isa 指针指向动态生成的派生类。在给被监控属性赋值时,执行的是派生类的 setter 方法。

KVO 的通知依赖于 NSObject 的两个方法:**willChangeValueForKey:** 和 **didChangeValueForKey:**。被观察属性发生变化时,对应的调用过程如下:

  • 被改变前,调用 **willChangeValueForKey:**,记录旧值
  • 改变被观察属性
  • 被改变后,调用 **didChangeValueForKey:**,记录新值
  • 接着调用 observeValueForKey:ofObject:change:context:

实现原理的过程图:

KVO 的不足

KVO 很明显的一个问题是,提供了一个单一回调。所有的属性变化,都会通过同一个方法回调。在内部再通过 key 来 ifelse 判断分别处理。理想的情况是,我们希望对应的属性变化,只触发对应的回调,而不影响其他属性。

我们可以通过手动实现 KVO 来解决这个问题。

自己实现 KVO

前面我们已经知道 KVO 的实现原理。大致流程为:

  • 当某个类的对象被观察时
  • 系统会动态创建一个该类的派生类,派生类 superClass 指向原类
  • 修改被观察对象的 isa 指针,使其指向派生类
  • 在派生类中重写 setter 方法,在方法中加入 observer

实现源码比较长,附带在最后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//
// NSObject+KVO.h
// Demo
//
// Created by JonyFang on 2016/3/25.
// Copyright © 2016 JonyFang. All rights reserved.
//

#import <Foundation/Foundation.h>

typedef void(^FFObserverHandler)(id object, NSString *key, id oldValue, id newValue);

@interface NSObject (KVO)

- (void)ff_addObserver:(NSObject *)object forKey:(NSString *)key withBlock:(FFObserverHandler)handler;
- (void)ff_removeObserver:(NSObject *)object forKey:(NSString *)key;

@end
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
//
// NSObject+KVO.m
// Demo
//
// Created by JonyFang on 2016/3/25.
// Copyright © 2016 JonyFang. All rights reserved.
//

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

static NSString *const kFFKVOClassPrefix = @"FFObserver_";
static NSString *const kFFKVOAssociatedObject = @"FFKVOAssociatedObject";

@interface FFKVObserver : NSObject

@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *key;
@property (nonatomic, copy) FFObserverHandler handler;

@end

@implementation FFKVObserver

- (instancetype)initWithObserver:(NSObject *)observer forKey:(NSString *)key handler:(FFObserverHandler)handler {
if (self = [super init]) {
_observer = observer;
_key = key;
_handler = handler;
}
return self;
}

@end

// key = propertyName,以 person 为例
// 过程:person -> setPerson:
static NSString *setterForKey(NSString *key) {
if (key.length <= 0) { return nil; }
NSString *firstStr = [[key substringToIndex:1] uppercaseString];
NSString *leaveStr = [key substringFromIndex:1];
return [NSString stringWithFormat:@"set%@%@:", firstStr, leaveStr];
}

// 过程:setPerson: -> person
static NSString *getterBySetter(NSString *setter) {
if (setter.length <= 0 || ![setter hasPrefix: @"set"] || ![setter hasSuffix: @":"]) {
return nil;
}
NSRange range = NSMakeRange(3, setter.length - 4);
NSString *getter = [setter substringWithRange: range];
NSString *firstStr = [[getter substringToIndex: 1] lowercaseString];
getter = [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString: firstStr];

return getter;
}

static void KVO_Setter(id self, SEL _cmd, id newValue) {
NSString *setterName = NSStringFromSelector(_cmd);
NSString *getterName = getterBySetter(setterName);
if (!getterName) {
@throw [NSException exceptionWithName: NSInvalidArgumentException reason: [NSString stringWithFormat: @"unrecognized selector sent to instance %p", self] userInfo: nil];
return;
}

id oldValue = [self valueForKey: getterName];
struct objc_super superClass = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self))
};

[self willChangeValueForKey: getterName];
void (*objc_msgSendSuperKVO)(void *, SEL, id) = (void *)objc_msgSendSuper;
objc_msgSendSuperKVO(&superClass, _cmd, newValue);
[self didChangeValueForKey: getterName];

//获取所有监听回调对象进行回调
NSMutableArray * observers = objc_getAssociatedObject(self, (__bridge const void *)kFFKVOAssociatedObject);
for (FFKVObserver *ob in observers) {
if ([ob.key isEqualToString: getterName]) {
dispatch_async(dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
ob.handler(self, getterName, oldValue, newValue);
});
}
}
}

static Class KVO_Class(id self) {
return class_getSuperclass(object_getClass(self));
}

// ==================

@implementation NSObject (KVO)

- (void)ff_addObserver:(NSObject *)object forKey:(NSString *)key withBlock:(FFObserverHandler)handler {
//step 1 get setter method, if not, throw exception
SEL setterSelector = NSSelectorFromString(setterForKey(key));
Method setterMethod = class_getInstanceMethod([self class], setterSelector);
if (!setterMethod) {
@throw [NSException exceptionWithName: NSInvalidArgumentException reason: [NSString stringWithFormat: @"unrecognized selector sent to instance %@", self] userInfo: nil];
return;
}

// 自己的类作为被观察者类
Class observedClass = object_getClass(self);
NSString *className = NSStringFromClass(observedClass);

// 如果被监听者没有 `FFObserver_`,创建新类
if (![className hasPrefix: kFFKVOClassPrefix]) {
// 为被观察对象的类创建一个新的带有 `FFObserver_` 前缀的子类
observedClass = [self createKVOClassWithOriginalClassName:className];
// 创建新的子类,并添加新的方法
// object 内部的 isa 变量指向它的 class。这个变量可以被改变,而不需要重新创建
// 这一步即创建我们需要的新 class
object_setClass(self, observedClass);
}

// 判断是否有方法
if (![self hasSelector:setterSelector]) {
const char * types = method_getTypeEncoding(setterMethod);
// 将原来的 setter 方法替换为新的 setter 方法
// 通过 runtime 的 Method Swizzling
class_addMethod(observedClass, setterSelector, (IMP)KVO_Setter, types);
}

// 新建一个观察者类
// 这个类的实现写在同一个 class,相当于导入一个类:`FFKVOObserver`
// 这个类的作用是观察者,负责 Block 回调
FFKVObserver *observer = [[FFKVObserver alloc] initWithObserver:object forKey:key handler:handler];

// 因为观察者实例都有前缀 `FFKVOAssociatedObject`
// 通过共同前缀,获取`观察者数组`
// 再将新建的 observer 加入数组
//
// 因为 objc_getAssociatedObject 的参数要求,需要转换为 void
// 在 ARC 有效时,通过 (__bridge void *) 能够实现 id 和 void * 的相互转换
NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge void *)kFFKVOAssociatedObject);
// 若没有新建
if (!observers) {
observers = [NSMutableArray array];
objc_setAssociatedObject(self, (__bridge void *)kFFKVOAssociatedObject, observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
[observers addObject:observer];
}

- (void)ff_removeObserver:(NSObject *)object forKey:(NSString *)key {
NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge void *)kFFKVOAssociatedObject);

FFKVObserver *tmp = nil;
for (FFKVObserver *ob in observers) {
if (ob.observer == object && [ob.key isEqualToString: key]) {
tmp = ob;
break;
}
}
[observers removeObject:tmp];
}

- (Class)createKVOClassWithOriginalClassName:(NSString *)className {
NSString *kvoClassName = [kFFKVOClassPrefix stringByAppendingString: className];
Class observedClass = NSClassFromString(kvoClassName);

if (observedClass) { return observedClass; }

// 创建以 `FFObserver_` 为类名前缀的新类
Class originalClass = object_getClass(self);
Class kvoClass = objc_allocateClassPair(originalClass, kvoClassName.UTF8String, 0);

// 获取监听对象的 class 方法实现代码,然后替换新建类的 class 实现
Method classMethod = class_getInstanceMethod(originalClass, @selector(class));
const char *types = method_getTypeEncoding(classMethod);
class_addMethod(kvoClass, @selector(class), (IMP)KVO_Class, types);
objc_registerClassPair(kvoClass);
return kvoClass;
}

- (BOOL)hasSelector:(SEL)selector {
Class observedClass = object_getClass(self);
unsigned int methodCount = 0;
Method *methodList = class_copyMethodList(observedClass, &methodCount);
for (int i = 0; i < methodCount; i++) {
SEL thisSelector = method_getName(methodList[i]);
if (thisSelector == selector) {
free(methodList);
return YES;
}
}

free(methodList);
return NO;
}

@end

本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

本站由 @JonyFang 创建,使用 Stellar 作为主题,您可以在 GitHub 找到本站源码。