KVO源码阅读。

尝试去读一遍KVO的源码,发现它比KVC复杂的多,只能明白个大概。
文章最后按照自己的理解,再结合源码的实现流程自己写了一个KVO,锻炼锻炼。

一、常用接口

1
2
3
4
5
6
7
8
9
10
11
// KVO的回调
@interface NSObject(NSKeyValueObserving)
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
@end

// 添加及移除观察者
@interface NSObject(NSKeyValueObserverRegistration)
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
@end

二、技巧回顾

1.如何使得属性在未改变的情况下不发送通知(手动控制)?

示例如下,主要有:

  1. 重写automaticallyNotifiesObserversForKey:方法
  2. 重写setter方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@interface YAObject : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation YAObject
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
if ([key isEqualToString:@"name"]) return NO;
return [super automaticallyNotifiesObserversForKey:key];
}

- (void)setName:(NSString *)name {
if (name != _name) {
[self willChangeValueForKey:@"name"];
_name = name;
[self didChangeValueForKey:@"name"];
}
}
@end

2.如何注册依赖键(多个属性影响到某一个属性)?

  1. 重写getter方法,定义依赖关系
  2. 重写keyPathsForValuesAffectingValueForKey:方法,添加依赖的key集合
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
@interface YAObject : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
@end
@implementation YAObject
- (NSString *)name {
// 定义依赖关系
return [NSString stringWithFormat:@"firstName is %@ and lastName is %@", self.firstName, self.lastName];
}

+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"name"]) {
keyPaths = [keyPaths setByAddingObjectsFromArray:@[@"firstName", @"lastName"]];
}
return keyPaths;
}

/*
// 也可以命名为keyPathsForValuesAffecting<Key>的类方法来达到同样的目的
+ (NSSet<NSString *> *)keyPathsForValuesAffectingName {
return [NSSet setWithObjects:@"firstName", @"lastName", nil];
}
*/
@end

3.当NSMutableArray中元素增加和减少时如何监听到?

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@interface YAObject : NSObject
@property (nonatomic, strong) NSMutableArray *nameList;
@end
@implementation YAObject
// 实现集合代理对象: 一般建议-countOf<Key>和-objectIn<Key>AtIndex:
- (NSUInteger)countOfNameList {
return [_nameList count];
}

- (id)objectInNameListAtIndex:(NSUInteger)index {
return [_nameList objectAtIndex:index];
}

// 至少实现一个插入方法和一个删除方法
- (void)insertObject:(id)object inNameListAtIndex:(NSUInteger)index {
[_nameList insertObject:object atIndex:index];
}

- (void)removeObjectFromNameListAtIndex:(NSUInteger)index {
[_nameList removeObjectAtIndex:index];
}
@end

使用:

  1. 务必使用mutableArrayValueForKey获取集合
  2. 使用insertObject方法添加元素

比如:

1
2
NSMutableArray *nameList = [self.obj mutableArrayValueForKey:@"nameList"];
[nameList insertObject:obj atIndex:0];

三、几个问题

总结的几个问题。

1.KVO的实现原理是什么?

Objective-C依托于强大的runtime机制来实现KVO。当我们第一次观察某个对象的属性时,runtime会创建一个新的继承自这个对象的class的subclass(前缀是NSKVONotifying_)。在这个新的subclass中,它会重写所有被观察的key的setter,然后将object的isa指针指向新创建的class(这个指针告诉Objective-C运行时某个object到底是什么类型的)。所以object神奇地变成了新的子类的实例。

——摘自南峰子的博客。

新类重写了setter方法、class方法、dealloc方法和_isKVOA方法。如果自己生成重名的NSKVONotifying_XX类,会造成KVO失效。以监听YAPerson对象的age属性为例,主要逻辑的伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@interface YAPerson : NSObject
@property (nonatomic, assign) NSUInteger age;
@end
@implementation YAPerson
@end

@interface NSKVONotifying_YAPerson : YAPerson
@end
@implementation NSKVONotifying_YAPerson
- (void)setAge:(NSUInteger)age {
_NSSetUnsignedLongLongValueAndNotify();
}

void _NSSetUnsignedLongLongValueAndNotify() {
[self willChangeValueForKey:@"age"];
[super setAge:age];
[self didChangeValueForKey:@"age"];
}

- (void)didChangeValueForKey:(NSString *)key {
[observer observeValueForKeyPath:@"age" ofObject:self change:change context:context];
}
@end

2.KVO源码中添加观察者时整体的大致流程是什么?

  1. keyPathclass等信息封装成NSKeyValueProperty,分别解析一般属性(@"aa")、可计算属性(@"@aa")、属性链(@"aa.bb.@cc.dd"),进行子类化,缓存在CFMutableSet中方便下次快速取出。
  2. NSKeyValuePropertycontextoptionsobserver等信息封装成NSKeyValueObservance,缓存在NSHashTable中。
  3. 倘若设置了NSKeyValueObservingOptionInitial选项,会在注册观察服务时调用一次触发方法。
  4. 动态创建名为NSKVONotifying_+原来类名的新类,重写其dealloc_isKVOA方法,再重写class方法,利用object_setClass()函数将其isa指针指向原先的类。
  5. 重写willChangeValueForKey:didChangeValueForKey:方法,重写被观察属性的setter方法,在setter中先调用willChangeValueForKey:方法,然后调用父类的 setter 方法对成员变量赋值,之后再调用 didChangeValueForKey: 方法。
  6. didChangeValueForKey: 方法中会调用observeValueForKeyPath:ofObject:change:context:方法。

3.KVO中所封装组件的关系是怎样的?

  1. keyPath、class等信息封装成NSKeyValueProperty,使用CFMutableSet缓存NSKeyValueProperty
  2. observer、property、options、context 、originalObservable等信息封装成NSKeyValueObservance使用NSHashTable(NSKeyValueShareableObservationInfos)缓存。
  3. NSKeyValueObservationInfoNSKeyValueObservance的关系是: NSKeyValueObservationInfo中有一个observances数组,数组里面是NSKeyValueObservance对象。
  4. 每一个object都有一个observationInfo属性(void *类型),它与NSKeyValueObservationInfo会相互转化。

classkeyPath决定了是否是同一个NSKeyValueProperty
NSKeyValuePropertyObserveroptionscontext 决定了是否是同一个NSKeyValueObservance

4.KVO多次使用完全相同的参数进行addObserver操作,也会得到相应次数的回调,如何做到?

1
2
3
4
[self.obj addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
[self.obj addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
[self.obj addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
[self.obj addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];

如上面代码,会有四次回调。

在每次添加观察者时,都会获取NSKeyValueObservance对象(可能从缓存中获取也可能新建),并把它追加到object的observances数组中(即使该数组中已经存在完全相同(指针一致)的NSKeyValueObservance对象),由此保证了多次addObserver操作会有多次回调。

通过打印可以证明:

5.如何手动触发KVO?

手动调用willChangeValueForKey:didChangeValueForKey:。也即:

1
2
3
4
- (void)invokeKVOForAge {
[self.person willChangeValueForKey:@"age"];
[self.person didChangeValueForKey:@"age"];
}

只手动调用一个还不行,为什么?由原理可知,didChangeValueForKey:中才真正调用了observeValueForKeyPath:ofObject:change:context方法,所以didChangeValueForKey:毋庸置疑不可或缺。那么为什么还需要willChangeValueForKey:呢?
查看文档,苹果一如既往地冷酷:

Calls to this method are always paired with a matching call to willChangeValueForKey:.

意思是,这俩是成对存在的,你只管调就是了。好吧,这块源码确实没看明白,先挖个坑吧。

6.通过KVC修改属性会触发KVO吗?

当KVC是通过setter方法设值时,无需多言,会触发KVO。当KVC是通过成员变量设值时,也会触发KVO,为什么?
阅读源码可知,直接通过成员变量设值,会创建NSKeyValueIvarSetter对象,在该对象的构造方法中会调用_NSSetValueAndNotifyForKeyInIvar()函数,其实现如下:

1
2
3
4
5
6
7
void _NSSetValueAndNotifyForKeyInIvar(id object, SEL selector, id value, NSString *key, Ivar ivar, IMP imp) {
[object willChangeValueForKey:key];

((void (*)(id,SEL,id,NSString *, Ivar))imp)(object,NULL,value,key,ivar);

[object didChangeValueForKey:key];
}

真相大白了,它调用了willChangeValueForKey:didChangeValueForKey:方法,正是这个触发了KVO。需要注意的是,直接修改成员变量是不会触发KVO的

四、添加观察者

这一步骤中封装出了许多的类,同时也把产生的许多对象做了进一步的缓存处理。

1.封装

1.1接口方法

加锁,接口方法中使用pthread中的pthread_mutex_lock()pthread_mutex_unlock函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 这是个接口方法, 添加观察者分为两个流程: 1,根据class和keyPath获取NSKeyValueProperty对象。2,添加对property的观察。
- (void)d_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {
// 加锁
pthread_mutex_lock(&_NSKeyValueObserverRegistrationLock);
// 获取当前线程pthread
_NSKeyValueObserverRegistrationLockOwner = pthread_self();
// 根据class和keyPath获取NSKeyValueProperty
NSKeyValueProperty *property = NSKeyValuePropertyForIsaAndKeyPath(object_getClass(self),keyPath);
// 添加对property的观察
[self _d_addObserver:observer forProperty:property options:options context:context];
// 解锁
pthread_mutex_unlock(&_NSKeyValueObserverRegistrationLock);
}

1.2.添加对NSKeyValueProperty的观察的具体实现

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
// 添加观察者过程中, 不是单纯地'观察'keyPath, 而是观察对keyPath封装的NSKeyValueProperty
- (void)_addObserver:(id)observer forProperty:(NSKeyValueProperty *)property options:(int)options context:(void *)context {
if(options & NSKeyValueObservingOptionInitial) {
// NSKeyValueObservingOptionInitial: 观察最初的值(在注册观察服务时会调用一次触发方法)
NSString *keyPath = [property keyPath];
_NSKeyValueObserverRegistrationLockOwner = NULL;
// 解锁
pthread_mutex_unlock(&_NSKeyValueObserverRegistrationLock);

id newValue = nil;
if (options & NSKeyValueObservingOptionNew) {
// newValue就是当前的值
newValue = [self valueForKeyPath:keyPath];
if (!newValue) {
newValue = [NSNull null]; // 使用NSNull对象
}
}

NSKeyValueChangeDictionary *changeDictionary = nil;
// 创建NSKeyValueChangeDetails结构体
/*
typedef struct {
NSKeyValueChange kind;
id oldValue;
id newValue;
NSIndexSet *indexes;
id extraData;
} NSKeyValueChangeDetails;
*/
NSKeyValueChangeDetails changeDetails = {0};
changeDetails.kind = NSKeyValueChangeSetting;
changeDetails.oldValue = nil;
changeDetails.newValue = newValue;
changeDetails.indexes = nil;
changeDetails.extraData = nil;
// 函数1: 通知观察者, 传递结构体changeDetails
NSKeyValueNotifyObserver(observer,keyPath, self, context, nil, NO,changeDetails, &changeDictionary);

[changeDictionary release];
// 加锁
pthread_mutex_lock(&_NSKeyValueObserverRegistrationLock);
// 获取当前pthread
_NSKeyValueObserverRegistrationLockOwner = pthread_self();
}

// 函数2: 获取oldObservationInfo
NSKeyValueObservationInfo *oldObservationInfo = _NSKeyValueRetainedObservationInfoForObject(self,property.containerClass);

BOOL cacheHit = NO;
NSKeyValueObservance *addedObservance = nil;
id originalObservable = nil;

if((options >> 8) & 0x01) {
// _CFGetTSD: 获取线程信息
// Get some thread specific data from a pre-assigned slot.
NSKeyValueObservingTSD *TSD = _CFGetTSD(NSKeyValueObservingTSDKey);
if (TSD) {
originalObservable = TSD->implicitObservanceAdditionInfo.originalObservable;
}
}
// 函数3: 获取newObservationInfo
NSKeyValueObservationInfo *newObservationInfo = _NSKeyValueObservationInfoCreateByAdding(oldObservationInfo, observer, property, options, context, originalObservable,&cacheHit,&addedObservance);

// 函数4: 将self的observationInfo设置为newObservationInfo
_NSKeyValueReplaceObservationInfoForObject(self,property.containerClass,oldObservationInfo,newObservationInfo);

// - (void)object:(id)object didAddObservance:(NSKeyValueObservance *)observance recurse:(BOOL)recurse {}
// 实际上这个方法啥事也没做
[property object:self didAddObservance:addedObservance recurse:YES];

// 核心方法: 获取property中已经修改过的class
Class isaForAutonotifying = [property isaForAutonotifying];
if(isaForAutonotifying) {
Class cls = object_getClass(self);
if(cls != isaForAutonotifying) {
// 通过 object_setClass()修改isa指针, 设置自己的class为property的isaForAutonotifying
object_setClass(self,isaForAutonotifying);
}
}

[newObservationInfo release];
[oldObservationInfo release];
}

函数1: 设置changeDictionary并调用NSKVONotify()函数

1
2
3
4
5
6
7
8
9
10
11
12
void NSKeyValueNotifyObserver(id observer,NSString * keyPath, id object, void *context, id originalObservable, BOOL isPriorNotification, NSKeyValueChangeDetails changeDetails, NSKeyValueChangeDictionary **changeDictionary) {
if(*changeDictionary) {
[*changeDictionary setDetailsNoCopy:changeDetails originalObservable:originalObservable];
} else {
*changeDictionary = [[NSKeyValueChangeDictionary alloc] initWithDetailsNoCopy:changeDetails originalObservable:originalObservable isPriorNotification:isPriorNotification];
}
NSUInteger retainCountBefore = [*changeDictionary retainCount];
NSKVONotify(observer, keyPath, object, *changeDictionary, context);
if(retainCountBefore != (NSUInteger)INTMAX_MAX && retainCountBefore != [*changeDictionary retainCount]) {
[*changeDictionary retainObjects];
}
}
1
2
3
4
5
// NSKVONotify()函数就是调用observeValueForKeyPath方法
void NSKVONotify(id observer, NSString *keyPath, id object, NSDictionary *changeDictionary, void *context) {
NSKeyValueObservingAssertRegistrationLockNotHeld();
[observer observeValueForKeyPath:keyPath ofObject:object change:changeDictionary context:context];
}

函数2: 获取object的observationInfo对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
NSKeyValueObservationInfo *_NSKeyValueRetainedObservationInfoForObject(id object, NSKeyValueContainerClass *containerClass) {
NSKeyValueObservationInfo *observationInfo = nil;
os_lock_lock(&NSKeyValueObservationInfoSpinLock);
if (containerClass) {
// 调用containerClass的cachedObservationInfoImplementation实现
observationInfo = ((NSKeyValueObservationInfo * (*)(id,SEL))containerClass.cachedObservationInfoImplementation)(object, @selector(observationInfo));
} else {
// 直接获取object的d_observationInfo对象
observationInfo = (NSKeyValueObservationInfo *)[object d_observationInfo];
}
[observationInfo retain];
os_lock_unlock(&NSKeyValueObservationInfoSpinLock);
return observationInfo;
}

函数3: 获取’添加观察者’时所需要的NSKeyValueObservationInfo

如果baseObservationInfo存在,则一顿封装操作后,会把封装完毕的NSKeyValueObservance“追加”到baseObservationInfoobservances数组中。如果baseObservationInfo不存在,则一顿封装操作后,会把封装完毕的NSKeyValueObservance放到新创建的NSKeyValueObservationInfo对象的observances数组中。最后,cacheHit告诉调用者是否有命中缓存,*addedObservance指向了observance对象。

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
NSKeyValueObservationInfo *_NSKeyValueObservationInfoCreateByAdding(NSKeyValueObservationInfo *baseObservationInfo, id observer, NSKeyValueProperty *property, int options, void *context, id originalObservable,  BOOL *cacheHit, NSKeyValueObservance **addedObservance) {
NSKeyValueObservationInfo *createdObservationInfo = nil;

os_lock_lock(&NSKeyValueObservationInfoCreationSpinLock);

// 使用弱引用表NSKeyValueShareableObservationInfos缓存观察者对象
if(!NSKeyValueShareableObservationInfos) {
// 自定义NSPointerFunctions
NSPointerFunctions *pointerFunctions = [[NSPointerFunctions alloc] initWithOptions:NSPointerFunctionsWeakMemory];
// 设置hash函数
[pointerFunctions setHashFunction:NSKeyValueShareableObservationInfoNSHTHash];
// 设置判等函数
[pointerFunctions setIsEqualFunction:NSKeyValueShareableObservationInfoNSHTIsEqual];
// 创建NSHashTable
NSKeyValueShareableObservationInfos = [[NSHashTable alloc] initWithPointerFunctions:pointerFunctions capacity:0];
}

if(!NSKeyValueShareableObservationInfoKeyIsa) {
NSKeyValueShareableObservationInfoKeyIsa = [NSKeyValueShareableObservationInfoKey class];
}

// 通过这个公共key到缓存表NSKeyValueShareableObservationInfos中查找
static NSKeyValueShareableObservationInfoKey * shareableObservationInfoKey;

if(!shareableObservationInfoKey) {
// 第一次使用, 为空时创建
shareableObservationInfoKey = [[NSKeyValueShareableObservationInfoKey alloc] init];
}
// 设置key的信息
shareableObservationInfoKey.addingNotRemoving = YES;
shareableObservationInfoKey.baseObservationInfo = baseObservationInfo;
shareableObservationInfoKey.additionObserver = observer;
shareableObservationInfoKey.additionProperty = property;
shareableObservationInfoKey.additionOptions = options;
shareableObservationInfoKey.additionContext = context;
shareableObservationInfoKey.additionOriginalObservable = originalObservable;

// 根据shareableObservationInfoKey的已有信息进行查找
NSKeyValueObservationInfo * existsObservationInfo = [NSKeyValueShareableObservationInfos member:shareableObservationInfoKey];
// 清空shareableObservationInfoKey的废弃信息(主要是减少对observer的引用计数)
shareableObservationInfoKey.additionOriginalObservable = nil;
shareableObservationInfoKey.additionObserver = nil;
shareableObservationInfoKey.baseObservationInfo = nil;

if(!existsObservationInfo) { // 缓存中不存在
// (一般是第一次使用时)NSHashTable为空, 创建
if(!NSKeyValueShareableObservances) {
NSKeyValueShareableObservances = [NSHashTable weakObjectsHashTable];
}
// 通过这个公共key到缓存表NSKeyValueShareableObservances中查找
static NSKeyValueShareableObservanceKey *shareableObservanceKey;

if(!shareableObservanceKey) { // key不存在时创建
shareableObservanceKey = [[NSKeyValueShareableObservanceKey alloc] init];
}
// 设置key的信息
shareableObservanceKey.observer = observer;
shareableObservanceKey.property = property;
shareableObservanceKey.options = options;
shareableObservanceKey.context = context;
shareableObservanceKey.originalObservable = originalObservable;

// 查找Observance缓存
NSKeyValueObservance *existsObservance = [NSKeyValueShareableObservances member:shareableObservanceKey];
// 清空shareableObservanceKey的废弃信息
shareableObservanceKey.originalObservable = nil;
shareableObservanceKey.observer = nil;

NSKeyValueObservance *observance = nil;

if (!existsObservance) {
// 没有找到, 则创建observance
observance = [[NSKeyValueObservance alloc] _initWithObserver:observer property:property options:options context:context originalObservable:originalObservable];
if(observance.cachedIsShareable) {
// 可以缓存, 放入NSKeyValueShareableObservances中
[NSKeyValueShareableObservances addObject:observance];
}
} else {
// 找到了, observance就指向existsObservance
observance = existsObservance;
}

if (baseObservationInfo) {
// 复制baseObservationInfo并追加observance
createdObservationInfo = [baseObservationInfo _copyByAddingObservance:observance];
} else {
// 创建新的ObservationInfo
createdObservationInfo = [[NSKeyValueObservationInfo alloc] _initWithObservances:&observance count:1 hashValue:0];
}

if (createdObservationInfo.cachedIsShareable){
// 允许缓存, 添加到NSKeyValueShareableObservationInfos中
[NSKeyValueShareableObservationInfos addObject:createdObservationInfo];
}
// 没有命中缓存
*cacheHit = NO;
// 设置新添加的Observance
*addedObservance = observance;
} else {
// 缓存中存在
// 设置命中缓存
*cacheHit = YES;
// observance必定就是已存在的info.observance列表最后一个, 因为判断equal就是按照这个原则去判断的
// 判等函数: NSKeyValueShareableObservationInfoNSHTIsEqual()
*addedObservance = existsObservationInfo.observances.lastObject;
// 设置createdObservationInfo
createdObservationInfo = existsObservationInfo;
}

// 解锁
os_lock_unlock(&NSKeyValueObservationInfoCreationSpinLock);
return createdObservationInfo;
}

函数4:将object的observationInfo设置为newObservationInfo

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
void _NSKeyValueReplaceObservationInfoForObject(id object, NSKeyValueContainerClass * containerClass, NSKeyValueObservationInfo *oldObservationInfo, NSKeyValueObservationInfo *newObservationInfo) {
os_lock_lock(&NSKeyValueObservationInfoSpinLock);

if (newObservationInfo) [newObservationInfo retain];

// 不理解??????
/*
typedef struct {
CFMutableArrayRef pendingArray;//0
ObservationInfoWatcher *firstWatcher;//4
ImplicitObservanceAdditionInfo implicitObservanceAdditionInfo;
ImplicitObservanceRemovalInfo implicitObservanceRemovalInfo;
} NSKeyValueObservingTSD;
*/
NSKeyValueObservingTSD *TSD = _CFGetTSD(NSKeyValueObservingTSDKey);
if(TSD) {
ObservationInfoWatcher *next = TSD->firstWatcher;
while(next) {
if (next->object == object) {
[next->observationInfo release];
next->observationInfo = [newObservationInfo retain];
break;
}
next = next->next;
}
}

if(containerClass) {
// 调用object的d_setObservationInfo:方法, 并传参数newObservationInfo
containerClass.cacheNSetObservationInfoImplementation(object, @selector(d_setObservationInfo:), newObservationInfo);
} else {
// 直接设置新值
[object d_setObservationInfo: newObservationInfo];
}
os_lock_unlock(&NSKeyValueObservationInfoSpinLock);
}

2.缓存

缓存查找逻辑是一致的: 确定这些对象的hashisEqual:方法,通过创建与目标对象判等属性一致的key去查找。

2.1.NSKeyValueProperty的缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if(!NSKeyValueProperties) {
// 创建集合NSKeyValueProperties
CFSetCallBacks callbacks = {0};
callbacks.version = kCFTypeSetCallBacks.version;
callbacks.retain = kCFTypeSetCallBacks.retain;
callbacks.release = kCFTypeSetCallBacks.release;
callbacks.copyDescription = kCFTypeSetCallBacks.copyDescription;
// 设置CFSet集合中元素判等的依据
callbacks.equal = (CFSetEqualCallBack)NSKeyValuePropertyIsEqual;
// 设置CFSet集合中元素的hash值获取函数
callbacks.hash = (CFSetHashCallBack)NSKeyValuePropertyHash;
NSKeyValueProperties = CFSetCreateMutable(NULL, 0, &callbacks);
}
// 把property添加到NSKeyValueProperties集合中
CFSetAddValue(NSKeyValueProperties, property);

CFSet集合中元素判等的依据

1
2
3
4
BOOL NSKeyValuePropertyIsEqual(NSKeyValueProperty *property1, NSKeyValueProperty *property2) {
return (property1.containerClass == property2.containerClass) &&
(property1.keyPath == property2.keyPath || [property1.keyPath isEqual: property2.keyPath]);
}

返回NSKeyValueProperty的hash值

1
2
3
NSUInteger NSKeyValuePropertyHash(NSKeyValueProperty *property) {
return property.keyPath.hash ^ (NSUInteger)(void *)property.containerClass;
}

再次证明了 classkeyPath决定了是否是同一个NSKeyValueProperty

2.2.NSKeyValueObservance的缓存

缓存查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
NSHashTable *NSKeyValueShareableObservances;
if(!NSKeyValueShareableObservances) {
NSKeyValueShareableObservances = [NSHashTable weakObjectsHashTable];
}

// observance查找key
static DNSKeyValueShareableObservanceKey *shareableObservanceKey;
if(!shareableObservanceKey) {
shareableObservanceKey = [[DNSKeyValueShareableObservanceKey alloc] init];
}
shareableObservanceKey.observer = observer;
shareableObservanceKey.property = property;
shareableObservanceKey.options = options;
shareableObservanceKey.context = context;
shareableObservanceKey.originalObservable = originalObservable;

// 查找缓存
NSKeyValueObservance *existsObservance = [NSKeyValueShareableObservances member:shareableObservanceKey];
shareableObservanceKey.originalObservable = nil;
shareableObservanceKey.observer = nil;

重写NSKeyValueObservancehashisEqual:方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (NSUInteger)hash {
return _NSKVOPointersHash(5, _observer, _property, (void *)(NSUInteger)(_options), _context, _originalObservable);
}

- (BOOL)isEqual:(id)object {
if (object == self) return YES;
if (![object isKindOfClass: self.class]) return NO;
NSKeyValueObservance *other = (NSKeyValueObservance *)object;

return other.observer == self.observer &&
other.options == self.options &&
other.context == self.context &&
other.originalObservable == self.originalObservable;
}

2.3.NSKeyValueObservationInfo的缓存

缓存查找

1
2
3
4
5
6
7
8
9
NSHashTable *NSKeyValueShareableObservationInfos;

// observationInfo缓存
if(!NSKeyValueShareableObservationInfos) {
NSPointerFunctions *pointerFunctions = [[NSPointerFunctions alloc] initWithOptions:NSPointerFunctionsWeakMemory];
[pointerFunctions setHashFunction:NSKeyValueShareableObservationInfoNSHTHash];
[pointerFunctions setIsEqualFunction:NSKeyValueShareableObservationInfoNSHTIsEqual];
NSKeyValueShareableObservationInfos = [[NSHashTable alloc] initWithPointerFunctions:pointerFunctions capacity:0];
}

2.4.NSKeyValueObservationInfo的存储(observationInfo)

当封装成NSKeyValueObservationInfo时,weakNSHashTable并不负责存储,那么,谁负责真正的存储呢?

Take or return a pointer that identifies information about all of the observers that are registered with the receiver, the options that were used at registration-time, etc. The default implementation of these methoNS store observation info in a global dictionary keyed by the receivers’ pointers. For improved performance, you can override these methoNS to store the opaque data pointer in an instance variable. Overrides of these methoNS must not attempt to send Objective-C messages to the passed-in observation info, including -retain and -release.

这个方法的默认实现是以对象的指针作为key,从一个全局的字典中获取信息。

如何获取对象的指针?这里有个定义:
OBSERVATION_INFO_KEY的定义是: #define OBSERVATION_INFO_KEY(object) ((void *)(~(NSUInteger)(object)))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
CFMutableDictionaryRef NSKeyValueObservationInfoPerObject = NULL;
- (void *)observationInfo {
return NSKeyValueObservationInfoPerObject ? (void *)CFDictionaryGetValue(NSKeyValueObservationInfoPerObject, OBSERVATION_INFO_KEY(self)) : NULL;
}

- (void)setObservationInfo:(void *)info {
if(!NSKeyValueObservationInfoPerObject) {
NSKeyValueObservationInfoPerObject = CFDictionaryCreateMutable(NULL, 0, NULL, NULL);
}
if (info) {
CFDictionarySetValue(NSKeyValueObservationInfoPerObject, OBSERVATION_INFO_KEY(self), info);
} else {
CFDictionaryRemoveValue(NSKeyValueObservationInfoPerObject, OBSERVATION_INFO_KEY(self));
}
}

即这个方法的默认实现是以对象的指针作为key,从一个全局的字典中获取信息。由此,我们可以理解为,KVO的信息是存储在一个全局字典中,而不是存储在对象本身。
不过,为了提高效率,我们可以重写observationInfo属性的set和get方法,以将这个不透明的数据指针存储到一个实例变量中。但是,在重写时,我们不应该尝试去向这些数据发送一个Objective-C消息,包括retain和release。

3.新建与重写

在这一步骤中,动态创建了原来class的子类,当然,也重写和添加了许多方法。

3.1动态创建子类的核心实现

动态创建子类中,重写了dealloc、class、_isKVOA方法。

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
NSKeyValueNotifyingInfo *_NSKVONotifyingCreateInfoWithOriginalClass(Class originalClass) {
static IMP NSObjectWillChange;
static IMP NSObjectDidChange;

// 构造新的子类名
const char *originalClassName = class_getName(originalClass);
size_t size = strlen(originalClassName) + 16;
char *newClassName = (char *)malloc(size);

// #define NOTIFY_CLASSNAME_PREFIX "NSKVONotifying_"
strlcpy(newClassName, NOTIFY_CLASSNAME_PREFIX, size);
strlcat(newClassName, originalClassName, size);

// 创建子类
Class newSubClass = objc_allocateClassPair(originalClass, newClassName, sizeof(NSKeyValueNotifyingInfo));
objc_registerClassPair(newSubClass);

free(newClassName);

unsigned char *ivars = object_getIndexedIvars(newSubClass);
// 创建NSKeyValueNotifyingInfo对象, 封装子类\原始类等信息
NSKeyValueNotifyingInfo *notifyingInfo = (NSKeyValueNotifyingInfo *)ivars;
notifyingInfo->originalClass = originalClass;
notifyingInfo->newSubClass = newSubClass;
notifyingInfo->notifyingKeys = CFSetCreateMutable(NULL, 0, &kCFCopyStringSetCallBacks);
notifyingInfo->selKeyMap = CFDictionaryCreateMutable(NULL, 0, NULL, &kCFTypeDictionaryValueCallBacks);

pthread_mutexattr_t mutexattr;
pthread_mutexattr_init(&mutexattr);
pthread_mutexattr_settype(&mutexattr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&notifyingInfo->mutex, &mutexattr);
pthread_mutexattr_destroy(&mutexattr);

// 获取NSObject类的 willChangeValueForKey 和 didChangeValueForKey实现
static dispatch_once_t NSObjectIMPLookupOnce;
dispatch_once(&NSObjectIMPLookupOnce, ^{
NSObjectWillChange = class_getMethodImplementation([NSObject class], @selector(d_willChangeValueForKey:));
NSObjectDidChange = class_getMethodImplementation([NSObject class], @selector(d_didChangeValueForKey:));
});

// 判断originalClass类是否重写了 willChangeValueForKey 或 didChangeValueForKey
// 就是拿NSObject的实现与originalClass的实现做对比(函数指针IMP比较)
notifyingInfo->overrideWillOrDidChange = class_getMethodImplementation(notifyingInfo->originalClass, @selector(d_willChangeValueForKey:)) != NSObjectWillChange || class_getMethodImplementation(notifyingInfo->originalClass, @selector(d_didChangeValueForKey:)) != NSObjectDidChange;
// 对notifyingInfo的originalClass添加 _isKVOA方法
NSKVONotifyingSetMethodImplementation(notifyingInfo, ISKVOA_SELECTOR, (IMP)NSKVOIsAutonotifying, NULL);
// 对notifyingInfo的originalClass添加 dealloc方法
NSKVONotifyingSetMethodImplementation(notifyingInfo, @selector(dealloc), (IMP)NSKVODeallocate, NULL);
// 对notifyingInfo的originalClass添加 class方法
NSKVONotifyingSetMethodImplementation(notifyingInfo, @selector(class), (IMP)NSKVOClass, NULL);

return notifyingInfo;
}

添加_isKVOA方法

1
2
3
BOOL NSKVOIsAutonotifying() {
return YES;
}

重写class方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Class NSKVOClass(id object, SEL selector) {
// 新的class: NSKVONotifying_XXXX
Class currentClass = object_getClass(object);
// 原先的class: XXXX
Class originalClass = _NSKVONotifyingOriginalClassForIsa(currentClass);
if (currentClass == originalClass) {
// 相同, 返回object的currentClass
Method m = class_getInstanceMethod(currentClass, selector);
return ((Class (*)(id,Method))method_invoke)(object, m);
} else {
// 不同, 返回originalClass
return [originalClass class];
}
}

重写dealloc方法

获取object对应的observationInfo(对象)并把它放到结构体中,在调用完object原先的dealloc方法之后判断observationInfo是否还存在,若存在说明observer没有在dealloc之前被移除掉,进而抛出异常。

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
void NSKVODeallocate(id object, SEL selector) {
// 获取object对应的observationInfo
NSKeyValueObservationInfo *observationInfo = _NSKeyValueRetainedObservationInfoForObject(object, nil);
ObservationInfoWatcher watcher = {object, observationInfo, NULL};
_NSKeyValueAddObservationInfoWatcher(&watcher);
// 获取notifyInfo
NSKeyValueNotifyingInfo *notifyInfo = (NSKeyValueNotifyingInfo *)object_getIndexedIvars(object_getClass(object));

// 调用object原来的dealloc实现
Method originDellocMethod = class_getInstanceMethod(notifyInfo->originalClass, selector);
((id (*)(id,Method))method_invoke)(object, originDellocMethod);

@try {
if(watcher.observationInfo) {
// observationInfo不存在才对, 如果还存在, 说明没有正确地移除observer
BOOL keyExistsAndHasValidFormat = false;
BOOL cleansUpBeforeThrowing = false;

cleansUpBeforeThrowing = (BOOL)CFPreferencesGetAppBooleanValue(CFSTR("NSKVODeallocateCleansUpBeforeThrowing"), kCFPreferencesCurrentApplication, (Boolean *)&keyExistsAndHasValidFormat);
// key存在且key对应的value为YES
cleansUpBeforeThrowing = cleansUpBeforeThrowing && keyExistsAndHasValidFormat;
// dyld_get_program_sdk_version返回系统版本
if (dyld_get_program_sdk_version() > 0x7FFFF || cleansUpBeforeThrowing) {
if (cleansUpBeforeThrowing) {
_NSKeyValueRemoveObservationInfoForObject(object, watcher.observationInfo);
}
[NSException raise:NSInternalInconsistencyException format:@"An instance %p of class %@ was deallocated while key value observers were still registered with it. Current observation info: %@", object, notifyInfo->originalClass, watcher.observationInfo];
}
else {
NSKVODeallocateBreak(object);
}
}

}
@catch (NSException *exception) {
[exception raise];
}
@finally {
// 移除watcher
_NSKeyValueRemoveObservationInfoWatcher(&watcher);

[watcher.observationInfo release];
}
}

4.回调通知

上文分析,NSKeyValueNotifyObserver()就是回调的函数,通过它调用observeValueForKeyPath:ofObject:change:context:方法。这里还剩下最后一个问题,回调是怎么处理的?

NSKVONotifyingEnableForInfoAndKey()函数中重写setter方法,之后在NSSetPrimitiveValueAndNotify()函数中先调用willChangeValueForKey,再调用原先的setter方法,再调用didChangeValueForKey

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static inline void NSSetPrimitiveValueAndNotify(id object,SEL selector, void (^setValueWithImplementation)(IMP imp)) {
NSKeyValueNotifyingInfo *info = object_getIndexedIvars(object_getClass(object));
pthread_mutex_lock(&info->mutex);
NSString *key = CFDictionaryGetValue(info->selKeyMap, selector);
key = [key copyWithZone:nil];
pthread_mutex_unlock(&info->mutex);
if (info->overrideWillOrDidChange) {
[object willChangeValueForKey:key];
IMP imp = class_getMethodImplementation(info->originalClass, selector);
setValueWithImplementation(imp);
[object didChangeValueForKey:key];
} else {
[object changeValueForKey:key key:nil key:nil usingBlock:^{
IMP imp = class_getMethodImplementation(info->originalClass, selector);
setValueWithImplementation(imp);
}];
}
[key release];
}

当然,这里的didChangeValueForKey也被重写实现了,它会调用真正的回调observeValueForKeyPath:ofObject:change:context:方法。

五、移除观察者

找到NSKeyValueObservance移除即可。

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
NSKeyValueObservationInfo *_NSKeyValueObservationInfoCreateByRemoving(NSKeyValueObservationInfo *baseObservationInfo, id observer, NSKeyValueProperty *property, void *context, BOOL shouldCompareContext,  id originalObservable,  BOOL *cacheHit, NSKeyValueObservance **removalObservance) {
NSKeyValueObservationInfo *createdObservationInfo = nil;

// 当前已经存在的observance的数量
NSUInteger observanceCount = CFArrayGetCount((CFArrayRef)baseObservationInfo.observances);
NSKeyValueObservance *observancesBuff[observanceCount];
CFArrayGetValues((CFArrayRef)baseObservationInfo.observances, CFRangeMake(0, observanceCount), (const void **)observancesBuff);

NSUInteger removalObservanceIndex = NSNotFound;

for (NSInteger i = observanceCount - 1; i >= 0; --i) {
// 逐个遍历observancesBuff数组中的元素
NSKeyValueObservance *observance = observancesBuff[i];
// property和observer一致
if (observance.property == property && observance.observer == observer) {
// 不需要比较context或者context一致
if (!shouldCompareContext || observance.context == context) {
// originalObservable一致
if (!originalObservable || observance.originalObservable == originalObservable) {
// 需要移除的observance
*removalObservance = observance;
// 确定了将要移除的observance的索引
removalObservanceIndex = i;
break;
}
}
}
}

// 已经找到需要移除的observance
if (*removalObservance) {
// 原先observance的数量大于1个
if (observanceCount > 1) {
os_lock_lock(&NSKeyValueObservationInfoCreationSpinLock);
// NSKeyValueShareableObservationInfos缓存不存在, 创建
if (!NSKeyValueShareableObservationInfos) {
NSPointerFunctions *functions = [[NSPointerFunctions alloc] initWithOptions:NSPointerFunctionsWeakMemory];
[functions setHashFunction:NSKeyValueShareableObservationInfoNSHTHash];
[functions setIsEqualFunction:NSKeyValueShareableObservationInfoNSHTIsEqual];

NSKeyValueShareableObservationInfos = [[NSHashTable alloc] initWithPointerFunctions:functions capacity:0];

[functions release];
}
if (!NSKeyValueShareableObservationInfoKeyIsa) {
// 就是NSKeyValueShareableObservationInfoKey.class
NSKeyValueShareableObservationInfoKeyIsa = NSKeyValueShareableObservationInfoKey.self;
}

static NSKeyValueShareableObservationInfoKey * shareableObservationInfoKey = nil;
// 构建查找缓存的Key
if (!shareableObservationInfoKey) {
shareableObservationInfoKey = [[NSKeyValueShareableObservationInfoKey alloc] init];
}

shareableObservationInfoKey.addingNotRemoving = NO;
shareableObservationInfoKey.baseObservationInfo = baseObservationInfo;
shareableObservationInfoKey.removalObservance = *removalObservance;
shareableObservationInfoKey.removalObservanceIndex = removalObservanceIndex;
shareableObservationInfoKey.cachedHash = NSKeyValueShareableObservationInfoNSHTHash(shareableObservationInfoKey, NULL);

// 尝试在缓存中查找NSKeyValueObservationInfo
NSKeyValueObservationInfo *existsObservationInfo = [NSKeyValueShareableObservationInfos member:shareableObservationInfoKey];

// 重置key的数据
shareableObservationInfoKey.removalObservance = nil;
shareableObservationInfoKey.baseObservationInfo = nil;

NSUInteger cachedHash = shareableObservationInfoKey.cachedHash;

shareableObservationInfoKey.cachedHash = 0;

if (!existsObservationInfo) {
// 在缓存中没有找到, 移除removalObservanceIndex对应的元素
memmove(observancesBuff + removalObservanceIndex, observancesBuff + removalObservanceIndex + 1, (observanceCount - (removalObservanceIndex + 1)) * sizeof(NSKeyValueObservance *));
// 重新创建ObservationInfo, 数量为observanceCount - 1
createdObservationInfo = [[NSKeyValueObservationInfo alloc] _initWithObservances:observancesBuff count:observanceCount - 1 hashValue:cachedHash];
if (createdObservationInfo.cachedIsShareable) {
// 缓存ObservationInfo
[NSKeyValueShareableObservationInfos addObject:createdObservationInfo];
}
// 没有命中缓存
*cacheHit = NO;
} else {
// 命中缓存
*cacheHit = YES;
// 直接赋值existsObservationInfo
createdObservationInfo = [existsObservationInfo retain];
}

os_lock_unlock(&NSKeyValueObservationInfoCreationSpinLock);

return createdObservationInfo;
}
else {
// 原先只有一个observance, 命中缓存
*cacheHit = YES;
}
}
// 没有找到需要移除的observance, 返回nil
return nil;
}

六、KVO自实现

有很多同学尝试自己实现了KVO,有按照原生接口的,也有自我发挥直接传递block的。由于之前我已经读过一些开源的代码,见《「KVOController」的封装》,作者就是使用了block很好地封装了KVO的回调。所以,这里还是试着按照原生接口实现一下。

由于对源码理解地不是十分透彻,再加上能力有限,在尝试实现过程中遇到不少问题,幸好都解决了。当然,代码肯定有不少问题的,而且仅仅实现一点核心功能,姑且当做玩具看看吧。

1.接口

1
2
3
4
5
6
7
8
9
@interface NSObject(YAKVO)
@property void *ya_observationInfo;

- (void)ya_willChangeValueForKey:(NSString *)key;
- (void)ya_didChangeValueForKey:(NSString *)key;
- (void)ya_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
- (void)ya_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(void *)context;
- (void)ya_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context;
@end

2.实现

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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
#define ClassPrefixCStr "YAKVONotifying_" // 新类的前缀
#define ClassPrefix @ ClassPrefixCStr
#define OBSERVATION_INFO_KEY(object) ((void *)(~(NSUInteger)(object)))
static NSMutableDictionary *YAKeyValueChangeDictionary = nil;

/// 一些私有方法和属性
@interface NSObject(YAKVOPrivate)
@end
@implementation NSObject(YAKVOPrivate)
- (BOOL)ya_isKVOClass {
return NO;
}

- (void)ya_changeValueForKey:(NSString *)key usingBlock:(void (^)(void))block {
[self ya_willChangeValueForKey:key];
if (block) block();
[self ya_didChangeValueForKey:key];
}
@end



/// 包装keyPath和originalClass
@interface YAKeyValueProperty : NSObject
@property (nonatomic, assign) Class isaForAutonotifying;
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, assign) Class originalClass;
- (instancetype)initWithOriginalClass:(Class)originalClass keyPath:(NSString *)keyPath;
@end
@implementation YAKeyValueProperty
- (instancetype)initWithOriginalClass:(Class)originalClass
keyPath:(NSString *)keyPath {
if (self = [super init]) {
_originalClass = originalClass;
_keyPath = keyPath;
}
return self;
}

- (Class)isaForAutonotifying {
// 构造新的子类名
const char *originalClassName = class_getName(_originalClass);
size_t size = strlen(originalClassName) + 16;
char *newClassName = (char *)malloc(size);

strlcpy(newClassName, ClassPrefixCStr, size);
strlcat(newClassName, originalClassName, size);

// 创建子类
Class newSubClass = objc_allocateClassPair(_originalClass, newClassName, 0);
objc_registerClassPair(newSubClass);
free(newClassName);

// Setter方法替换
NSString *uppercase= [[_keyPath substringToIndex:1] uppercaseString];
NSString *last = [_keyPath substringFromIndex:1];
NSString *setter = [NSString stringWithFormat:@"set%@%@:", uppercase, last];
SEL sel = NSSelectorFromString(setter);
Method method = class_getInstanceMethod(newSubClass, sel);
if (method) {
const char *typeEncoding = method_getTypeEncoding(method);
class_replaceMethod(newSubClass, sel, (IMP)YASetValueAndNotifyForKey, typeEncoding);
} else {
[[NSException exceptionWithName:@"缺少参数" reason:@"没有实现Setter方法" userInfo:nil] raise];
}

// class方法替换、ya_isKVOClass方法替换
YAKVONotifyingSetMethodImplementation(newSubClass, @selector(ya_isKVOClass), (IMP)YAKVOIsAutonotifying);
YAKVONotifyingSetMethodImplementation(newSubClass, @selector(class), (IMP)YAKVOClass);
return newSubClass;
}

// 对应originalClass的ya_isKVOClass方法
BOOL YAKVOIsAutonotifying(id object, SEL sel) {
return YES;
}

// 对应originalClass的class方法
Class YAKVOClass(id object, SEL sel) {
// 新的class: NSKVONotifying_XXXX
Class currentClass = object_getClass(object);
if ([object ya_isKVOClass]) {
NSString *clsStr = [NSStringFromClass(currentClass) stringByReplacingOccurrencesOfString:ClassPrefix withString:@""];
return NSClassFromString(clsStr);
}
return currentClass;
}

// 对应originalClass的setter方法
void YASetValueAndNotifyForKey(id obj, SEL sel, id value, IMP imp) {
NSString *key = [[NSStringFromSelector(sel) substringFromIndex:3] lowercaseString];
key = [key substringToIndex:key.length - 1];
[obj ya_changeValueForKey:key usingBlock:^{
Class cls = [obj class];
// 调用父类的setter方法
IMP superImp = class_getMethodImplementation(cls, sel);
((void (*)(id ,SEL , id))superImp)(obj, sel, value);
}];
}

// 对某个class添加实例方法
void YAKVONotifyingSetMethodImplementation(Class cls, SEL sel, IMP imp) {
Method originMethod = class_getInstanceMethod(cls, sel);
const char *encoding = NULL;
if (originMethod) {
encoding = method_getTypeEncoding(originMethod);
class_addMethod(cls, sel, imp, encoding);
}
}
@end



/// 包装property、observer、context、options
@interface YAKeyValueObservance : NSObject
@property (nonatomic, weak) YAKeyValueProperty *property;
@property (nonatomic, weak) id observer;
@property (nonatomic, assign) void *context;
@property (nonatomic, assign) int options;
- (instancetype)initWithObserver:(id)observer property:(YAKeyValueProperty *)property options:(int)options context:(void *)context;
@end
@implementation YAKeyValueObservance
- (instancetype)initWithObserver:(id)observer
property:(YAKeyValueProperty *)property
options:(int)options
context:(void *)context {
if (self = [super init]) {
_observer = observer;
_property = property;
_options = options;
_context = context;
}
return self;
}

- (NSUInteger)hash {
NSUInteger observerContextHash = [[NSString stringWithFormat:@"%p-%p", _observer, _context] hash];
return observerContextHash ^ _property.hash ^ _options;
}

- (BOOL)isEqual:(id)object {
if (object == self) return YES;
if (![object isKindOfClass:object_getClass(self)]) return NO;
YAKeyValueObservance *other = (YAKeyValueObservance *)object;
return other.observer == self.observer &&
other.options == self.options &&
other.context == self.context;
}
@end



/// 包装YAKeyValueObservance数组
@interface YAKeyValueObservationInfo : NSObject
@property (nonatomic, strong) NSArray <YAKeyValueObservance *> *observances;
- (instancetype)initWithObservances:(NSArray <YAKeyValueObservance *> *)observances
count:(NSUInteger)count;
@end
@implementation YAKeyValueObservationInfo
- (instancetype)initWithObservances:(NSArray<YAKeyValueObservance *> *)observances
count:(NSUInteger)count {
if (self = [super init]) {
_observances = [[NSArray alloc] initWithArray:observances];
}
return self;
}
@end



/// 配置YAKeyValueObservationInfoKey,去查询匹配的YAKeyValueObservationInfo
@interface YAKeyValueObservationInfoKey : NSObject
@property (nonatomic, strong) YAKeyValueObservationInfo *baseObservationInfo;
@property (nonatomic, strong) NSObject *additionObserver;
@property (nonatomic, strong) YAKeyValueProperty *additionProperty;
@property (nonatomic, assign) NSUInteger additionOptions;
@property (nonatomic, assign) void* additionContext;
@end
@implementation YAKeyValueObservationInfoKey
@end



#pragma mark - Private methods
BOOL YAKeyValuePropertyIsEqual(YAKeyValueProperty *property1, YAKeyValueProperty *property2) {
return (property1.originalClass == property2.originalClass) &&
(property1.keyPath == property2.keyPath || [property1.keyPath isEqual: property2.keyPath]);
}

NSUInteger YAKeyValuePropertyHash(YAKeyValueProperty *property) {
return property.keyPath.hash ^ (NSUInteger)(__bridge void *)property.originalClass;
}

// 获取YAKeyValueProperty
static inline YAKeyValueProperty *getKeyValueProperty(Class cls, NSString *keyPath) {
// 缓存集合
static CFMutableSetRef YAKeyValueProperties;
if(!YAKeyValueProperties) {
// 创建YAKeyValueProperties
CFSetCallBacks callbacks = {0};
callbacks.version = kCFTypeSetCallBacks.version;
callbacks.retain = kCFTypeSetCallBacks.retain;
callbacks.release = kCFTypeSetCallBacks.release;
callbacks.copyDescription = kCFTypeSetCallBacks.copyDescription;
callbacks.equal = (CFSetEqualCallBack)YAKeyValuePropertyIsEqual;
callbacks.hash = (CFSetHashCallBack)YAKeyValuePropertyHash;
YAKeyValueProperties = CFSetCreateMutable(NULL, 0, &callbacks);
}
static YAKeyValueProperty *finder;
if (!finder) finder = [YAKeyValueProperty new];
finder.originalClass = cls;
finder.keyPath = keyPath;
YAKeyValueProperty *property = CFSetGetValue(YAKeyValueProperties, (__bridge const void *)(finder));
if (!property) {
// 缓存中没有找到, 创建
property = [[YAKeyValueProperty alloc] initWithOriginalClass:cls keyPath:keyPath];
// 添加到缓存中
CFSetAddValue(YAKeyValueProperties, (__bridge const void *)(property));
}
return property;
}

// 获取YAKeyValueObservance
static inline YAKeyValueObservance *getKeyValueObservance(YAKeyValueProperty *property,
id observer,
void *context,
int options) {
static NSHashTable *YAKeyValueShareableObservances;
if (!YAKeyValueShareableObservances) {
YAKeyValueShareableObservances = [NSHashTable weakObjectsHashTable];
}
static YAKeyValueObservance *finder;
if (!finder) finder = [YAKeyValueObservance new];
finder.property = property;
finder.context = context;
finder.observer = observer;
finder.options = options;
YAKeyValueObservance *observance = [YAKeyValueShareableObservances member:finder];
if (!observance) {
// 缓存中没有找到, 创建
observance = [[YAKeyValueObservance alloc] initWithObserver:observer property:property options:options context:context];
// 添加到缓存中
[YAKeyValueShareableObservances addObject:observance];
}
return observance;
}

NSUInteger YAKeyValueObservationInfoNSHTHash(const void *item, NSUInteger (*size)(const void *item)) {
if (object_getClass((__bridge id)item) == YAKeyValueObservationInfoKey.class) {
YAKeyValueObservationInfoKey *key = (__bridge YAKeyValueObservationInfoKey *)item;
return key.baseObservationInfo.observances.firstObject.hash;
} else {
YAKeyValueObservationInfo *info = (__bridge YAKeyValueObservationInfo *)item;
return info.observances.firstObject.hash;
}
}

BOOL YAKeyValueObservationInfoNSHTIsEqual(const void *item1, const void *item2, NSUInteger (* size)(const void * item)) {
// 这里仅仅写了YAKeyValueObservationInfoKey与YAKeyValueObservationInfo的比较
if (object_getClass((__bridge id)item1) == YAKeyValueObservationInfoKey.class || object_getClass((__bridge id)item2) == YAKeyValueObservationInfoKey.class) {
YAKeyValueObservationInfo *info = nil;
YAKeyValueObservationInfoKey *key = nil;

// 确定哪一个是info, 哪一个是key
if (object_getClass((__bridge id)item1) == YAKeyValueObservationInfoKey.class) {
info = (__bridge YAKeyValueObservationInfo *)item2;
key = (__bridge YAKeyValueObservationInfoKey *)item1;
} else {
info = (__bridge YAKeyValueObservationInfo *)item1;
key = (__bridge YAKeyValueObservationInfoKey *)item2;
}
NSArray <YAKeyValueObservance *> *observancesInKey = key.baseObservationInfo.observances;
NSArray <YAKeyValueObservance *> *observancesInInfo = info.observances;
// key中observance的数量
NSUInteger countInkey = observancesInKey.count;
// info中observance的数量
NSUInteger countInInfo = observancesInInfo.count;
if (countInkey != countInInfo) return NO;
for (NSUInteger i = 0; i < countInkey; i++) {
// 保证每个observance完全匹配
if (observancesInKey[i] != observancesInInfo[i]) {
return NO;
}
}
return YES;
}
return NO;
}



#pragma mark - Public methods
@implementation NSObject(YAKVO)
CFMutableDictionaryRef YAKeyValueObservationInfoPerObject = NULL;
- (void *)ya_observationInfo {
return YAKeyValueObservationInfoPerObject ? (void *)CFDictionaryGetValue(YAKeyValueObservationInfoPerObject, OBSERVATION_INFO_KEY(self)) : NULL;
}

- (void)setYa_observationInfo:(void *)info {
if (!YAKeyValueObservationInfoPerObject) {
CFDictionaryValueCallBacks callbacks = {0};
callbacks.version = kCFTypeDictionaryKeyCallBacks.version;
callbacks.retain = kCFTypeDictionaryKeyCallBacks.retain;
callbacks.release = kCFTypeDictionaryKeyCallBacks.release;
callbacks.copyDescription = kCFTypeDictionaryKeyCallBacks.copyDescription;
YAKeyValueObservationInfoPerObject = CFDictionaryCreateMutable(NULL, 0, NULL, &callbacks);
}
if (info) {
CFDictionarySetValue(YAKeyValueObservationInfoPerObject, OBSERVATION_INFO_KEY(self), info);
} else {
CFDictionaryRemoveValue(YAKeyValueObservationInfoPerObject, OBSERVATION_INFO_KEY(self));
}
}

- (void)ya_willChangeValueForKey:(NSString *)key {
if (!YAKeyValueChangeDictionary) {
YAKeyValueChangeDictionary = [NSMutableDictionary dictionary];
}
id oldValue = nil;
oldValue = [self valueForKeyPath:key];
if (!oldValue) oldValue = [NSNull null];
[YAKeyValueChangeDictionary setObject:oldValue forKey:[NSString stringWithFormat:@"%p-old", self]];
}

- (void)ya_didChangeValueForKey:(NSString *)key {
if (self.ya_isKVOClass) {
YAKeyValueProperty *property = getKeyValueProperty(self.class, key);
YAKeyValueObservationInfo *observation = self.ya_observationInfo;
[observation.observances enumerateObjectsUsingBlock:^(YAKeyValueObservance *obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj.property isEqual:property]) {
NSMutableDictionary *change = [NSMutableDictionary dictionary];
if (obj.options & NSKeyValueObservingOptionOld) {
id old = [YAKeyValueChangeDictionary objectForKey:[NSString stringWithFormat:@"%p-old", self]];
[change setObject:old forKey:@"old"];
} else {
[YAKeyValueChangeDictionary removeObjectForKey:[NSString stringWithFormat:@"%p-old", self]];
}

if (obj.options & NSKeyValueObservingOptionNew) {
id newValue = nil;
newValue = [self valueForKeyPath:key];
if (!newValue) newValue = [NSNull null];
[YAKeyValueChangeDictionary setObject:newValue forKey:[NSString stringWithFormat:@"%p-new", self]];
[change setObject:newValue forKey:@"new"];
}
[obj.observer ya_observeValueForKeyPath:key ofObject:self change:change context:nil];
}
}];
}
}

- (void)ya_addObserver:(NSObject *)observer
forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(void *)context {

YAKeyValueProperty *property = getKeyValueProperty(self.class, keyPath);
YAKeyValueObservance *observance = getKeyValueObservance(property, observer, context, options);

static NSHashTable *YAKeyValueShareableObservationInfos;
if (!YAKeyValueShareableObservationInfos) {
NSPointerFunctions *pointerFunctions = [[NSPointerFunctions alloc] initWithOptions:NSPointerFunctionsWeakMemory];
[pointerFunctions setHashFunction:YAKeyValueObservationInfoNSHTHash];
[pointerFunctions setIsEqualFunction:YAKeyValueObservationInfoNSHTIsEqual];
YAKeyValueShareableObservationInfos = [[NSHashTable alloc] initWithPointerFunctions:pointerFunctions capacity:0];
}
static YAKeyValueObservationInfoKey *finder;
if (!finder) {
finder = [YAKeyValueObservationInfoKey new];
}

YAKeyValueObservationInfo *info = (__bridge id)[self ya_observationInfo];
finder.baseObservationInfo = info;
finder.additionObserver = observer;
finder.additionContext = context;
finder.additionOptions = options;
finder.additionProperty = property;

YAKeyValueObservationInfo *observation = [YAKeyValueShareableObservationInfos member:finder];
// 重置finder数据
finder.baseObservationInfo = nil;
finder.additionObserver = nil;
finder.additionContext = NULL;
finder.additionOptions = 0;
finder.additionProperty = nil;

if (!observation) {
// 缓存中没有找到, 创建
observation = [[YAKeyValueObservationInfo alloc] initWithObservances:@[observance] count:1];
// 添加到缓存中
[YAKeyValueShareableObservationInfos addObject:observation];
} else {
NSMutableArray *buffer = [NSMutableArray arrayWithArray:observation.observances];
[buffer addObject:observance];
observation.observances = [NSArray arrayWithArray:buffer];
}
self.ya_observationInfo = (__bridge void *)(observation);

if (!self.ya_isKVOClass) {
Class isaForAutonotifying = [property isaForAutonotifying];
// 更改isa指针
object_setClass(self, isaForAutonotifying);
}

if (options & NSKeyValueObservingOptionInitial) {
id newValue = nil;
if (options & NSKeyValueObservingOptionNew) {
newValue = [self valueForKeyPath:keyPath];
}
if (!newValue) newValue = [NSNull null]; // 使用NSNull对象
NSDictionary *change = @{@"new": newValue};
[observer ya_observeValueForKeyPath:keyPath ofObject:self change:change context:context];
}
}

- (void)ya_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(void *)context {
if (self.ya_isKVOClass) {
YAKeyValueProperty *property = getKeyValueProperty(self.class, keyPath);
YAKeyValueObservationInfo *observation = self.ya_observationInfo;
NSMutableArray *diff = [NSMutableArray arrayWithArray:observation.observances];
__block NSInteger removeIdx = -1;
[diff enumerateObjectsUsingBlock:^(YAKeyValueObservance *obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj.property isEqual:property] && obj.observer == observer && obj.context == context) {
removeIdx = idx;
*stop = YES;
}
}];
if (removeIdx != -1) {
// 找到需要移除的元素
[diff removeObjectAtIndex:removeIdx];
observation.observances = [NSArray arrayWithArray:diff];
}
}
}

- (void)ya_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{}
@end

功能check

1.添加观察者与设置回调:

1
2
3
4
5
6
7
 [self.obj ya_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:"NULL"];

- (void)ya_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"%@", change);
}
self.obj.name = @"Aaron";
self.obj.name = @"Jack";

打印:

1
2
3
4
5
6
7
8
2019-05-30 09:41:23.595046+0800 Aaron[24893:604622] {
new = Aaron;
old = "<null>";
}
2019-05-30 09:41:23.595215+0800 Aaron[24893:604622] {
new = Jack;
old = Aaron;
}

2.使用NSKeyValueObservingOptionInitial

1
2
[self.obj ya_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionInitial context:"NULL"];
self.obj.name = @"Aaron";

打印

1
2
3
2019-05-30 09:45:01.717131+0800 Aaron[25010:609398] {
new = "<null>";
}

3.多次添加观察者

1
2
3
4
[self.obj ya_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:"NULL"];
[self.obj ya_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:"NULL"];
[self.obj ya_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:"NULL"];
self.obj.name = @"Aaron";

打印

1
2
3
4
5
6
7
8
9
2019-05-30 09:47:20.625826+0800 Aaron[25107:613531] {
new = Aaron;
}
2019-05-30 09:47:20.625992+0800 Aaron[25107:613531] {
new = Aaron;
}
2019-05-30 09:47:20.626128+0800 Aaron[25107:613531] {
new = Aaron;
}

具体的代码放到了github上:https://github.com/ChenYalun/Project/tree/master/KVO

七、小结

这里学到一个技巧:如何确认NSUserDefaults中某个key是否存在?

比如

1
BOOL result = [NSUserDefaults.standardUserDefaults boolForKey:@"key"];

当result为NO时,怎么判断是存储键@"key"对应的value是NO,还是说压根就没有存过这个key呢?可以使用CFPreferencesGetAppBooleanValue()函数。
KVO中有这么一段代码:

1
2
3
4
5
6
BOOL keyExistsAndHasValidFormat = false; // key是否存在
BOOL cleansUpBeforeThrowing = false; // 存储的值为YES或者NO

cleansUpBeforeThrowing = (BOOL)CFPreferencesGetAppBooleanValue(CFSTR("key"), kCFPreferencesCurrentApplication, (Boolean *)&keyExistsAndHasValidFormat);
// 能判断出key存在
cleansUpBeforeThrowing = cleansUpBeforeThrowing && keyExistsAndHasValidFormat;

好了,说正题,本文简单总结了KVO的原理,并尝试阅读源码,然而能力有限、时间有限,只能明白个大概。在应用场景方面,就是“观察”,实际开发中,监听UIScrollView(及其子类)的属性比较多。最后,给自己挖了个坑:willChangeValueForKey:didChangeValueForKey:为什么要成对出现?哪天有时间了,再仔细看一看。


KVO的源码来自:https://github.com/renjinkui2719/DIS_KVC_KVO 。感谢作者。

参考文章
Foundation: NSKeyValueObserving(KVO)
如何自己动手实现 KVO
使用Block实现KVO
objc kvo简单探索
KVO 原理详解