学习KVO的封装。


KVOController源码只有700行左右,读一遍下来还是比较通畅的。这里做一个记录。

一、使用

使用起来极其简便。

1
2
3
4
// 设置所观察的对象及其keyPath
[self.KVOController observe:self.myButton keyPath:@"backgroundColor" options:NSKeyValueObservingOptionNew block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSKeyValueChangeKey,id> * _Nonnull change) {
NSLog(@"%@", change[NSKeyValueChangeNewKey]);
}];

这里的self.KVOController可以自己创建,也可以使用默认,因为KVOController是懒加载的。

一般情况下是像上面这样使用的,还有一种情况,不需要强持有被观察者的时候:

1
2
3
[self.KVOControllerNonRetaining observe:self.myButton keyPath:@"backgroundColor" options:NSKeyValueObservingOptionNew block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary<NSKeyValueChangeKey,id> * _Nonnull change) {
NSLog(@"%@", change[NSKeyValueChangeNewKey]);
}];

只需使用self.KVOControllerNonRetaining即可不增加被观察者self.myButton的引用计数。

二、分类

1
2
3
4
@interface NSObject (FBKVOController)
@property (nonatomic, strong) FBKVOController *KVOController;
@property (nonatomic, strong) FBKVOController *KVOControllerNonRetaining;
@end

要实现以上使用的方式,是给 NSObject 分类添加两个属性:KVOControllerKVOControllerNonRetaining。这个比较简单,使用Runtime 的关联属性即可。值得一提的是作者在 getter 方法里使用了懒加载,只有当使用到KVOController或者KVOControllerNonRetaining的时候,才会创建。当然,也可以选择自行创建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (FBKVOController *)KVOController {
id controller = objc_getAssociatedObject(self, NSObjectKVOControllerKey);
// 懒加载KVOController, 用到时才会创建
if (nil == controller) {
controller = [FBKVOController controllerWithObserver:self];
self.KVOController = controller;
}
return controller;
}

- (FBKVOController *)KVOControllerNonRetaining {
id controller = objc_getAssociatedObject(self, NSObjectKVOControllerNonRetainingKey);
if (nil == controller) {
controller = [[FBKVOController alloc] initWithObserver:self retainObserved:NO];
self.KVOControllerNonRetaining = controller;
}
return controller;
}

以上两个 getter 方法分别对应强引用被观察者和弱引用被观察者。

三、接口

由此可以看到,核心功能的实现依赖于FBKVOController

1
2
3
4
5
6
7
8
// 强引用
+ (instancetype)controllerWithObserver:(nullable id)observer;
- (instancetype)initWithObserver:(nullable id)observer;

// 指定构造器
- (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)retainObserved NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;

构造方法里主要暴露了两种初始化方式,其中通过initWithObserver这个方法可以设置参数retainObserved以表明是否需要强引用被观察者。

1
@property (nullable, nonatomic, weak, readonly) id observer;

只有一个只读属性,给出被观察者对象。

1
2
3
- (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block;
- (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options action:(SEL)action;
- (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;

作者给出了回调的三个选项:block回调,选择子回调以及 KVO 默认方法回调。可以在添加被观察者的时候自行选择。

1
2
3
- (void)observe:(nullable id)object keyPaths:(NSArray<NSString *> *)keyPaths options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block;
- (void)observe:(nullable id)object keyPaths:(NSArray<NSString *> *)keyPaths options:(NSKeyValueObservingOptions)options action:(SEL)action;
- (void)observe:(nullable id)object keyPaths:(NSArray<NSString *> *)keyPaths options:(NSKeyValueObservingOptions)options context:(nullable void *)context;

考虑到不一定只观察一个对象的一个成员变量,因此作者提供了keyPaths选项,可以同时观察一个对象的多个keyPath:传入一个字符串数组即可。

1
2
3
- (void)unobserve:(nullable id)object keyPath:(NSString *)keyPath;
- (void)unobserve:(nullable id)object;
- (void)unobserveAll;

移除监听提供三种接口:移除某个对象某个keyPath 的监听,移除对某个对象的监听,取消观察者对所有对象的所有监听。

四、FBKVOController实现

构造器

1
2
3
4
@implementation FBKVOController {
NSMapTable<id, NSMutableSet<_FBKVOInfo *> *> *_objectInfosMap;
pthread_mutex_t _lock;
}

FBKVOController主要维护了一个NSMapTablekey 是被观察的对象,valueNSMutableSet类型的集合(内部元素是_FBKVOInfo类型)。维护一个NSMapTable的原因是:便于观察一个对象的多个keyPath,这个对象作为 key,这许多个keyPath封装成一个个_FBKVOInfo存入NSMutableSet中。另外一个成员变量_lock主要是保证线程安全。

1
2
3
4
5
6
7
8
9
10
11
- (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)retainObserved {
self = [super init];
if (nil != self) {
_observer = observer;
// 根据是否retainObserved,选择NSMapTable的"强-强"或者"弱-强"
NSPointerFunctionsOptions keyOptions = retainObserved ? NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPointerPersonality : NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality;
_objectInfosMap = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality capacity:0];
pthread_mutex_init(&_lock, NULL);
}
return self;
}

FBKVOController所有暴露的构造方法接口都指向了上面的那个实现。这个方法只做了三件事: 1,初始化线程锁_lock,2,根据retainObserved参数创建不同类型的NSMapTable,是选择”强-强”还是选择”弱-强”。3,属性observer赋值。

由此可见,FBKVOController本身对观察者observer是弱引用的(有一个 weak 属性的observer成员变量),通过维护一个NSMapTable来最终确定对被观察者的强弱引用关系。

接口实现

1
2
3
4
5
6
7
8
9
- (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block {
if (nil == object || 0 == keyPath.length || NULL == block) {
return;
}
// 创建数据结构_FBKVOInfo
_FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options block:block];
// 添加对object的观察, 并传入info
[self _observe:object info:info];
}

以添加一个被观察者并且回调是 block 为例。在这个方法里首先是对参数的合理性判断,要求objectkeyPath以及block均是合理值。
接着把keyPathoptionsblock包装成一个数据结构_FBKVOInfo
最后调用自己的_observe:info:方法,传入objectinfo

_FBKVOInfo数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
@interface _FBKVOInfo : NSObject
@end

@implementation _FBKVOInfo {
@public
__weak FBKVOController *_controller;
NSString *_keyPath;
NSKeyValueObservingOptions _options;
SEL _action;
void *_context;
FBKVONotificationBlock _block;
_FBKVOInfoState _state;
}

_FBKVOInfo是一个数据结构,包含了监听的keyPathblock、选择子、context等等元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (instancetype)initWithController:(FBKVOController *)controller
keyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
block:(nullable FBKVONotificationBlock)block
action:(nullable SEL)action
context:(nullable void *)context {
self = [super init];
if (nil != self) {
_controller = controller;
_block = [block copy];
_keyPath = [keyPath copy];
_options = options;
_action = action;
_context = context;
}
return self;
}

构造方法的实现就是这样,不过有两个关键点:blockkeyPath调用一下 copy 方法。
keyPath调用一下 copy的原因是,这里的_keyPath是使用__strong修饰的,如果外面传进来的是不可变字符串,自然没有啥问题,可是一旦传进来一个可变字符串,如果直接赋值_keyPath = keyPath;,当这个可变字符串改变就会造成_keyPath也改变,比较容易产生不可控事件,所以调用 copy方法,也即是深复制浅复制的问题。

没有深复制的示例如下:

1
2
3
4
5
6
7
NSMutableString *str = [NSMutableString stringWithString:@"key"];
// 假定是_keyPath = keyPath;而不是_keyPath = [keyPath copy];
_FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:str options:NSKeyValueObservingOptionNew block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSKeyValueChangeKey,id> * _Nonnull change) {

} action:@selector(push) context:nil];
[str appendString:@"new"];
NSLog(@"%@", info->_keyPath); //info->_keyPath不符合预期的改变了

同样,blockcopy是把block从栈拷贝到堆中,防止被释放。因为block作为参数传入函数不会被 copy,依然在栈上,方法执行完立即释放的。

在ARC下:大部分情况下系统会把Block自动copy到堆上。

Block作为变量:
方法中声明一个 block 的时候是在栈上;
引用了外部局部变量或成员变量, 并且有赋值操作(有名字),会被 copy 到堆上;
赋值给附有__strong修饰符的id类型的类或者Blcok类型成员变量时;
赋值给一个 weak 变量不会被 copy;

Block作为属性:
用 copy 修饰会被 copy;

Block作为函数参数:
作为参数传入函数不会被 copy,依然在栈上,方法执行完即释放;
作为函数的返回值会被 copy 到堆;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (NSUInteger)hash {
return [_keyPath hash];
}

- (BOOL)isEqual:(id)object {
if (nil == object) {
return NO;
}
if (self == object) {
return YES;
}
if (![object isKindOfClass:[self class]]) {
return NO;
}
return [_keyPath isEqualToString:((_FBKVOInfo *)object)->_keyPath];
}

_FBKVOInfo还做了一点其他的事:
1.重写了- (NSUInteger)hash;方法,使用_keyPathhash 值来作为_FBKVOInfohash 值。分配的这个hash值(即用于查找集合中成员的位置标识),就是通过hash方法计算得来的,且hash方法返回的hash值最好唯一。
2.重写了- (BOOL)isEqual:(id)object;方法,满足Equal的条件有两个: 首先是类对象一致,再者是_keyPath匹配。换句话说,_keyPath决定了_FBKVOInfo是否是同一个。为了优化判等的效率,基于hashNSSetNSDictionary在判断成员是否相等时, 会这样做Step1: 成员的hash值是否和目标hash值相等,如果相同进入Step 2,如果不等,直接判断不相等
Step 2: hash值相同(即Step 1)的情况下,再进行对象判等, 作为判等的结果。

hash值是对象判等的必要非充分条件

NSPointerFunctionsObjectPointerPersonality对于 isEqual:hash 使用直接的指针比较。使用移位指针(shifted pointer)来做hash检测及确定两个对象是否相等;同时使用description方法来做描述字符串。

Personalities determine hashing and equality. NSPointerFunctionsObjectPersonality provides the standard Foundation behavior of using hash and isEqual:. You can also use NSPointerFunctionsObjectPointerPersonality, which treats the contents as objects, but uses direct pointer value comparison; this is useful if you need a collection to work with object identity rather than value.
NSPointerFunctionsObjectPointerPersonality 使用 ==判断相等
NSPointerFunctionsObjectPersonality 使用hashisEqual:判断相等

_observe:info:方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (void)_observe:(id)object info:(_FBKVOInfo *)info {
// lock
pthread_mutex_lock(&_lock);
NSMutableSet *infos = [_objectInfosMap objectForKey:object];
// 检查infos是否存在于_objectInfosMap中
_FBKVOInfo *existingInfo = [infos member:info];
if (nil != existingInfo) {
// 已经存在了,return
pthread_mutex_unlock(&_lock);
return;
}
// 不存在,创建infos并保存于_objectInfosMap中
if (nil == infos) {
infos = [NSMutableSet set];
[_objectInfosMap setObject:infos forKey:object];
}
// 把info添加到infos中
[infos addObject:info];
pthread_mutex_unlock(&_lock);
// _FBKVOSharedController添加观察
[[_FBKVOSharedController sharedController] observe:object info:info];
}

首先加锁。把被观察的对象object作为key从自己的_objectInfosMap获取其对应的NSMutableSet类型的集合,如果这个集合包含了已经封装好的info对象,说明已经对这个info添加过监听了,解锁直接 return 就是了。
如果这个infos集合不存在,创建。把info元素添加到这个infos集合中。解锁。调用[[_FBKVOSharedController sharedController] observe:object info:info];方法。

可见这个方法主要是使用_objectInfosMap保存了封装好的info对象,具体监听调用逻辑依赖于_FBKVOSharedController

五、_FBKVOSharedController实现

初始化

1
2
3
4
5
6
7
8
9
10
11
12

@interface _FBKVOSharedController : NSObject
+ (instancetype)sharedController;
- (void)observe:(id)object info:(nullable _FBKVOInfo *)info;
- (void)unobserve:(id)object info:(nullable _FBKVOInfo *)info;
- (void)unobserve:(id)object infos:(nullable NSSet *)infos;
@end

@implementation _FBKVOSharedController {
NSHashTable<_FBKVOInfo *> *_infos;
pthread_mutex_t _mutex;
}

_FBKVOSharedController是一个单例。作用是观察 _FBKVOInfo 中的 keyPath,并给予回调(回调的类型可以是 blockselector、系统回调方法)。

暴露出两个方法:

  1. 添加监听,参数为_infos
  2. 移除监听,参数为_FBKVOInfo或者NSSet类型的infos(容器内的元素仍然是_FBKVOInfo)

    内部维护了一个哈希表(NSHashTable)_infos,用于保存这些_FBKVOInfo。除此之外还有一个锁:_mutex,用于实现线程安全。

    哈希表的创建:

    1
    2
    3
    4
    NSHashTable *infos = [NSHashTable alloc];
    _infos = [infos initWithOptions:NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality capacity:0];
    NSPointerFunctionsWeakMemory: 持弱指针引用着_FBKVOInfo对象。
    NSPointerFunctionsObjectPointerPersonality 使用==判定相等。

可见_FBKVOSharedController只是单纯地掌管_FBKVOInfo集合,它只需要解析_FBKVOInfo并给observer回调即可,其他的一切都不关心。

添加监听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (void)observe:(id)object info:(nullable _FBKVOInfo *)info {
if (nil == info) return;
// _infos添加对象
pthread_mutex_lock(&_mutex);
[_infos addObject:info];
pthread_mutex_unlock(&_mutex);
// 添加观察,传入的context是info
[object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info];
if (info->_state == _FBKVOInfoStateInitial) {
info->_state = _FBKVOInfoStateObserving; // 初始状态转监听状态
} else if (info->_state == _FBKVOInfoStateNotObserving) { // 未监听状态便移除
// this could happen when `NSKeyValueObservingOptionInitial` is one of the NSKeyValueObservingOptions,
// and the observer is unregistered within the callback block.
// at this time the object has been registered as an observer (in Foundation KVO),
// so we can safely unobserve it.
// NSKeyValueObservingOptionInitial:添加观察者时就触发回调,并且在后面赋值时也会触发回调,但是都只返回NSKeyValueChangeKindKey。观察者在callback block中取消观察,所以在这里removeObserver
[object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
}
}

容器中添加 info 元素,添加监听。

移除监听

1
2
3
4
5
6
7
8
9
10
11
12
- (void)unobserve:(id)object info:(nullable _FBKVOInfo *)info {
if (nil == info) return;
// 哈希表中移除info
pthread_mutex_lock(&_mutex);
[_infos removeObject:info];
pthread_mutex_unlock(&_mutex);
// 移除监听
if (info->_state == _FBKVOInfoStateObserving) {
[object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
}
info->_state = _FBKVOInfoStateNotObserving;
}

容器中移除 info 元素,移除监听。

系统KVO调用

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)observeValueForKeyPath:(nullable NSString *)keyPath
ofObject:(nullable id)object
change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change
context:(nullable void *)context {
_FBKVOInfo *info;
{
pthread_mutex_lock(&_mutex);
// 看看info是否存在于_infos中,如果存在,返回info
info = [_infos member:(__bridge id)context];
pthread_mutex_unlock(&_mutex);
}

if (nil != info) {
// 局部变量强引用controller
FBKVOController *controller = info->_controller;
if (nil != controller) {
// 局部变量强引用observer
id observer = controller.observer;
if (nil != observer) {
if (info->_block) { // 回调 block
NSDictionary<NSKeyValueChangeKey, id> *changeWithKeyPath = change;
if (keyPath) {
NSMutableDictionary<NSString *, id> *mChange = [NSMutableDictionary dictionaryWithObject:keyPath forKey:FBKVONotificationKeyPathKey];
[mChange addEntriesFromDictionary:change];
changeWithKeyPath = [mChange copy];
}
info->_block(observer, object, changeWithKeyPath);
} else if (info->_action) { // 回调 selector
[observer performSelector:info->_action withObject:change withObject:object];
} else { // 调用KVO 系统方法
[observer observeValueForKeyPath:keyPath ofObject:object change:change context:info->_context];
}
}
}
}
}

最终在系统方法中给予不同类型的回调。

1
2
3
4
5
typedef NS_ENUM(uint8_t, _FBKVOInfoState) {
_FBKVOInfoStateInitial = 0,
_FBKVOInfoStateObserving,
_FBKVOInfoStateNotObserving,
};

作者使用了三个枚举值来记录监听状态。会不会是多此一举呢?不会。作用主要体现在添加监听的方法里有个移除监听操作:

1
2
3
4
5
6
7
8
 // 添加观察,传入的context是info
[object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info];

if (info->_state == _FBKVOInfoStateInitial) {
info->_state = _FBKVOInfoStateObserving; // 初始状态转监听状态
} else if (info->_state == _FBKVOInfoStateNotObserving) { // 未监听状态便移除
[object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
}

“未监听状态便移除“是怎么出现的?示例如下:

1
2
3
[self.KVOController observe:self.myButton keyPath:@"backgroundColor" options:NSKeyValueObservingOptionInitial block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary<NSKeyValueChangeKey,id> * _Nonnull change) {
[self.KVOController unobserve:self.myButton keyPath:@"backgroundColor"];
}];

包含了NSKeyValueObservingOptionInitial选项且在回调中移除了监听就会出现这种情况。因为如果有NSKeyValueObservingOptionInitial选项,在添加监听的时候就会有回调。调用栈如下:

执行到[object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info];这行代码的时候,首先添加监听,接着调用回调,而回调中又移除了观察,这时info的状态被设置为_FBKVOInfoStateNotObserving。接着进入了下面的 if-else 判断中,才有了移除监听的操作。可见逻辑非常严谨。

NSKeyValueObservingOption参考:

NSKeyValueObservingOptionNew:接收方法中使用change参数传入变化后的新值,键为:>NSKeyValueChangeNewKey;
NSKeyValueObservingOptionOld:接收方法中使用change参数传入变化前的旧值,键为:>NSKeyValueChangeOldKey;
NSKeyValueObservingOptionInitial:注册之后立刻调用接收方法,如果配置了>NSKeyValueObservingOptionNew,change参数内容会包含新值,键为:>NSKeyValueChangeNewKey;
NSKeyValueObservingOptionPrior:如果加入这个参数,接收方法会在变化前后分别调用一次,共两>次,变化前的通知change参数包含notificationIsPrior = 1。其他内容根据>NSKeyValueObservingOptionNew和NSKeyValueObservingOptionOld的配置确定。

六、一个函数

其实不是一个函数,不过是为了实现一个功能,核心还是一个函数。

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
// 1.枚举字符串化
static NSString *describe_option(NSKeyValueObservingOptions option) {
switch (option) {
case NSKeyValueObservingOptionNew:
return @"NSKeyValueObservingOptionNew";
break;
case NSKeyValueObservingOptionOld:
return @"NSKeyValueObservingOptionOld";
break;
case NSKeyValueObservingOptionInitial:
return @"NSKeyValueObservingOptionInitial";
break;
case NSKeyValueObservingOptionPrior:
return @"NSKeyValueObservingOptionPrior";
break;
default:
NSCAssert(NO, @"unexpected option %tu", option);
break;
}
return nil;
}

// 2.拼接option
static void append_option_description(NSMutableString *s, NSUInteger option) {
if (0 == s.length) {
[s appendString:describe_option(option)];
} else {
[s appendString:@"|"];
[s appendString:describe_option(option)];
}
}

// 3.NSKeyValueObservingOptions类型值遍历
static NSUInteger enumerate_flags(NSUInteger *ptrFlags) {
NSCAssert(ptrFlags, @"expected ptrFlags");
if (!ptrFlags) return 0;
NSUInteger flags = *ptrFlags;
if (!flags) return 0;
NSUInteger flag = 1 << __builtin_ctzl(flags);
flags &= ~flag;
*ptrFlags = flags;
return flag;
}

// 4.把options所有值都拼出来
static NSString *describe_options(NSKeyValueObservingOptions options) {
NSMutableString *s = [NSMutableString string];
NSUInteger option;
while (0 != (option = enumerate_flags(&options))) {
append_option_description(s, option);
}
return s;
}

不使用 switch-case 把位移枚举的值遍历出来了。

1
2
3
4
5
6
7
8
// Similar to __builtin_ctz, except the argument type is unsigned long.
// __builtin_ctz(x):x末尾0的个数
// 左移__builtin_ctz(x)位得到原先的枚举值
NSUInteger flag = 1 << __builtin_ctzl(flags);
// flags 去除已经取到的值
flags &= ~flag;
// 把更新后的flags赋予指向ptrFlags的指针
*ptrFlags = flags;

不失为一个好办法。

七、总结

使用 KVOController 进行键值观测可以说完美地解决了在使用原生 KVO 时遇到的各种问题。

1.不需要手动移除观察者;
2.实现 KVO 与事件发生处的代码上下文相同,不需要跨方法传参数;
3.使用 block 来替代方法能够减少使用的复杂度,提升使用 KVO 的体验;
4.每一个 keyPath 会对应一个属性,不需要在 block 中使用 if 判断 keyPath;

以上引自draveness。解释如下:
1.NSMapTable 可以持有键和值的弱引用,当键或者值当中的一个被释放时,整个这一项就会被移除掉。

1
2
3
// 这里使用被观察者observer作为强引用或者弱引用的key,使用_FBKVOInfo作为强引用的value
NSPointerFunctionsOptions keyOptions = retainObserved ? NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPointerPersonality : NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality;
_objectInfosMap = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality capacity:0];

2.因为是在@selector(observeValueForKeyPath:ofObject:change:context:)中处理的回调。
3.使用FBKVONotificationBlock。
4._FBKVOInfo封装。

纵观全部代码,作者首先给分类添加了两个属性,用于接口调用。这些属性都指向了FBKVOController,FBKVOController主要维护了一个NSMapTable。key 是所观察的对象,value 是NSMutableSet类型的集合,其内部元素是_FBKVOInfo类型对象。一个_FBKVOInfo对象对应一个信息封装。之所以使用NSMapTable集合是便于对同一个对象的多个keyPath进行观察,同时处理被观察者的强弱引用。另外_FBKVOInfo对象是对FBKVOController、keyPath、context、回调block等信息的封装。最后,各个FBKVOController把所有对观察的处理交给单例_FBKVOSharedController,这个单例调用系统KVO方法回调、处理包含所有_FBKVOInfo对象的NSHashTable集合。

参考资料
KVOController
iOS中Block使用注意点
isEqual与hash
iOS学习笔记——KVO