自定义KVO
上篇文章中我介绍了KVO的简单用法以及KVO的实现细节,为了加深对KVO的理解,我决定从头到尾介绍一下如何自定义KVO。
点进KVO的API,我发现所有的KVO的API都是通过类目的形式实现的:
因此,自定义KVO的第一步,就是创建一个NSObject的分类:
然后在分类中去增加一个自定义的添加观察者的方法,在该方法中,大致是做三件事情:
代码如下:
验证是否存在setter的代码如下:
下面来着重看一下中间子类是如何创建的:
首先会判断内存中是否已经存在了该动态子类,如果存在的话就直接返回,如果没有存在才会新建。
新建子类的时候,先申请开辟内存,然后注册类,然后给类添加class方法和属性的setter方法,也就是所谓的重写class方法和setter方法。
class方法的复写还好说,就是让其返回其父类也就是最开始的那个原类即可。
setter方法如何复写呢?setter中做了哪些事情呢?我们接下来就分析一下。
我现在想看一下当被KVO观测的属性值改变的时候,原来的setter方法里面做了哪些事情:
然后我在调试框中使用watchpoint来观测_name的变化:
我们看到,new value是空值,此时打开汇编调试,然后点击下一步:
此时新值已被设置,说明name的setter已经走了,此时再看堆栈:
找到了两个非常熟悉的方法名:willChangeValueForKey:和didChangeValueForKey:,这说明这两个方法在被KVO监测的属性的Setter中被调用了,我们之前也讲过,这两个方法是真正触发KVO的源头。而且我上篇文章也猜测过,在中间子类里面重写setter的时候会加上willChangeValueForKey:和didChangeValueForKey:,这里也验证了这个猜测。
而且通过查看堆栈信息我们也可以看到,最终还会调到LVPerson的setName,这说明在中间子类的setter中会调用父类的setter。
基于上面的分析,我就写出了下面的?norman_setter:
上面提到,我需要在对应的属性值改变的时候,向所有监听该改变的观察者们去发送消息,以通知其对该改变作出响应。
所以现在就有这样的一个问题,我如何找到这些观察者呢?
答案是我通过一个数组来保存这些观察者。
那么在什么时机进行保存呢?
答案是在添加观察者的时候就进行保存。
那么在保存的时候,我是保存哪些内容呢?不会是只保存观察者这一项吧?答案是,在添加KVO观察者的时候,会将本次观察的keyPath、观察的类型options、观察者等信息都封装进一个信息Model中,然后将这个Model存进一个数组里面:
保存KVO观察信息的数据模型的定义如下:
typedef NS_OPTIONS(NSUInteger, NormanKeyValueObservingOptions) {
NormanKeyValueObservingOptionNew = 0x01,
NormanKeyValueObservingOptionOld = 0x02,
};
@interface NormanKVOInfo : NSObject
@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, assign) NormanKeyValueObservingOptions options;
- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NormanKeyValueObservingOptions)options;
@end
@implementation NormanKVOInfo
- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NormanKeyValueObservingOptions)options {
if (self = [super init]) {
self.observer = observer;
self.keyPath = keyPath;
self.options = options;
}
return self;
}
@end
到这里,我们已经说完了添加观察者和KVO的响应,但是移除KVO观察者还没有说。
那么KVO观察者在移除的时候需要做什么事情呢?我们前面提到,当一个对象在被KVO监测之后,其isa指针会指向一个新的中间子类,因此,在移除KVO观测的时候,我们就需要将isa给指回来:
到这里,我已经将添加观察者、KVO监测响应、移除观察者都说完了,接下来将完整代码罗列出来。
承载KVO信息的NormanKVOInfo类:
typedef NS_OPTIONS(NSUInteger, NormanKeyValueObservingOptions) {
NormanKeyValueObservingOptionNew = 0x01,
NormanKeyValueObservingOptionOld = 0x02,
};
@interface NormanKVOInfo : NSObject
@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, assign) NormanKeyValueObservingOptions options;
- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NormanKeyValueObservingOptions)options;
@end
@implementation NormanKVOInfo
- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NormanKeyValueObservingOptions)options {
if (self = [super init]) {
self.observer = observer;
self.keyPath = keyPath;
self.options = options;
}
return self;
}
@end
KVO的实现——NSObject+NormanKVO类目:
@interface NSObject (NormanKVO)
// 添加KVO观察者
- (void)norman_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NormanKeyValueObservingOptions)options context:(nullable void *)context;
// KVO监测响应
- (void)norman_observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
// 移除KVO观察者
- (void)norman_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
@end
其实现如下:
#import "NSObject+NormanKVO.h"
#import <objc/message.h>
static NSString *const kNormanKVOPrefix = @"NormanKVONotifying_";
static NSString *const kNormanKVOAssiociateKey = @"kNormanKVOAssiociateKey";
@implementation NSObject (NormanKVO)
- (void)norman_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NormanKeyValueObservingOptions)options context:(void *)context {
// 1,验证是否存在setter(保证实例变量不会进来)
[self judgeSetterMethodFromKeyPath:keyPath];
// 2,动态生成子类
Class newClass = [self createChildClassWithKeyPath:keyPath];
// 3,修改isa指向(isa-swizzling)
object_setClass(self, newClass);
// 4,保存观察者信息
[self saveKVOInfoWithObserver:observer keyPath:keyPath options:options];
}
- (void)norman_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
}
- (void)norman_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {
NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kNormanKVOAssiociateKey));
if (observerArr.count<=0) {
return;
}
for (NormanKVOInfo *info in observerArr) {
if ([info.keyPath isEqualToString:keyPath]) {
[observerArr removeObject:info];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kNormanKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
break;
}
}
if (observerArr.count<=0) {
// 将isa指针指回中间子类的父类
Class superClass = [self class];
object_setClass(self, superClass);
}
}
#pragma mark - Private Methods
#pragma mark -- 验证是否存在setter方法
// 验证是否存在setter方法
- (void)judgeSetterMethodFromKeyPath:(NSString *)keyPath{
Class superClass = object_getClass(self);
SEL setterSeletor = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getInstanceMethod(superClass, setterSeletor);
if (!setterMethod) {
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"老铁没有当前%@的setter",keyPath] userInfo:nil];
}
}
// 从get方法获取set方法的名称 key ===>>> setKey:
static NSString *setterForGetter(NSString *getter) {
if (getter.length <= 0) { return nil;}
NSString *firstString = [[getter substringToIndex:1] uppercaseString];
NSString *leaveString = [getter substringFromIndex:1];
return [NSString stringWithFormat:@"set%@%@:",firstString,leaveString];
}
// 从set方法获取getter方法的名称 set<Key>:===> key
static NSString *getterForSetter(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 *firstString = [[getter substringToIndex:1] lowercaseString];
return [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstString];
}
#pragma mark -- 动态生成子类
- (Class)createChildClassWithKeyPath:(NSString *)keyPath {
NSString *oldClassName = NSStringFromClass([self class]);
NSString *newClassName = [NSString stringWithFormat:@"%@%@", kNormanKVOPrefix, oldClassName];
Class newClass = NSClassFromString(newClassName);
// 如果该中间子类不存在,则创建生成新类并注册
if (!newClass) {
/**
* 如果该中间子类在内存中不存在,则创建生成新类,需要传如下三个参数:
* 参数一:父类
* 参数二:类的名字
* 参数三:新类的开辟的额外空间
*/
// 2.1 : 申请类
newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
// 2.2 : 注册类
objc_registerClassPair(newClass);
// 2.3.1 : 给新类添加class方法 : 新类中的class指向的是新类的父类,也就是最初的原类
SEL classSEL = NSSelectorFromString(@"class");
Method classMethod = class_getInstanceMethod([self class], classSEL);
const char *classTypes = method_getTypeEncoding(classMethod);
class_addMethod(newClass, classSEL, (IMP)norman_class, classTypes);
}
// 2.3.2 : 给新类添加setter方法
SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getInstanceMethod([self class], setterSEL);
const char *setterTypes = method_getTypeEncoding(setterMethod);
class_addMethod(newClass, setterSEL, (IMP)norman_setter, setterTypes);
return newClass;
}
Class norman_class(id self,SEL _cmd) {
return class_getSuperclass(object_getClass(self));
}
static void norman_setter(id self, SEL _cmd, id newValue) {
// <1>,将setter消息转发给父类
void (*norman_msgSendSuper)(void *, SEL, id) = (void *)objc_msgSendSuper;
// void /* struct objc_super *super, SEL op, ... */
struct objc_super superStruct = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self)),
};
norman_msgSendSuper(&superStruct, _cmd, newValue);
// <2>,值发生变化之后,进行相关KVO的处理
// <2.1> : 拿到观察者
NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kNormanKVOAssiociateKey));
NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
id oldValue = [self valueForKey:keyPath];
for (NormanKVOInfo *info in observerArr) {
if ([info.keyPath isEqualToString:keyPath]) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSMutableDictionary<NSKeyValueChangeKey,id> *change = [NSMutableDictionary dictionaryWithCapacity:1];
// 对新、旧值进行处理
if (info.options & NormanKeyValueObservingOptionNew) {
[change setObject:newValue forKey:NSKeyValueChangeNewKey];
}
if (info.options & NormanKeyValueObservingOptionOld) {
[change setObject:@"" forKey:NSKeyValueChangeOldKey];
if (oldValue) {
[change setObject:oldValue forKey:NSKeyValueChangeOldKey];
}
}
// <2.2> : 将属性值变化的消息发送给观察者,以使观察者进行响应
SEL observerSEL = @selector(norman_observeValueForKeyPath:ofObject:change:context:);
objc_msgSend(info.observer, observerSEL, keyPath, self, change, NULL);
});
}
}
}
#pragma mark -- 保存观察者信息
- (void)saveKVOInfoWithObserver:(NSObject *)observer keyPath:(NSString *)keyPath options:(NormanKeyValueObservingOptions)options {
// 4.1 : 获取存储观察者信息的数组
NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kNormanKVOAssiociateKey));
if (!observerArr) {
// 4.2 : 如果存储观察者信息的数组不存在,就新建一个
observerArr = [NSMutableArray arrayWithCapacity:1];
}
// 4.3 : 将观察者信息封装成一个数据模型,并存放在数组中
NormanKVOInfo *info = [[NormanKVOInfo alloc] initWitObserver:observer forKeyPath:keyPath options:options];
[observerArr addObject:info];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kNormanKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
这里再完整总结一下自定义KVO的整个流程。
<一>添加观察者
1,首先,要检查一下传入的keyPath是否有对应的setter,如果没有的话,说明是成员变量,此时无法KVO,报出异常;如果有setter,那么就进入下一步。
2,动态生成中间子类
(1)首先看一下内存中是否有该子类,如果没有的话,就创建类并注册到内存。并且复写class方法使其指向动态子类的父类;如果中间子类已经存在则进入下一步。
(2)复写当前监测属性的setter方法,在复写的setter中做两件事情:①将setter消息转发到父类,调用父类的setter;②通过遍历对照keypath,找到当前变化的所有监听者,并给这些监听者发送一个监听到变化的消息,使之在外界对变化进行响应
3,isa-swizzling
将当前被KVO监听的实例对象的isa指针修改为指向中间子类
4,存储这条KVO信息
将当前的KVO信息封装成一个数据model,并保存到专门的数组中
<二>响应KVO
提供给外界一个接口,使之可以在KVO监听到变化的时候对变化进行处理。
<三>移除观察者
里面做的最重要的一件事就是将实例对象的isa指针的指向给改回来。
自定义KVO优化——函数式编程
现在各位想一下,我们实现一个KVO的监听,需要做哪些步骤呢?
首先你得添加观察者,添加完观察者你还需要去复写专门的方法来响应观测到的变化,监听完之后还得移除观察者。
哇,好麻烦啊~能不能优化一下呢?答案是能优化。那么怎么优化呢?这就要说到函数式编程的思想了。
关于函数式编程,我在之前的文章中有提到过:
函数式编程会将封装粒度降低到函数级别,它会将函数作为封装的基本单元,并且会减少中间不可变因素的产生。
在OC中,函数式编程可以通过Block来体现,因此,我们可以通过Block的形式将KVO的添加观察者和响应变化合二为一。
响应变化的Block的定义放在KVO信息模型中:
在添加KVO观察者的时候传一个handleBlock进来,然后将其保存在KVO信息Model中:
在对应的属性值发生变化的时候,回调该handle:
这样一改造之后,在外界使用的时候,KVO观察者的添加和KVO变化的监听就可以一起写了,而不必分开写了:
一个循环引用的小问题
接下来说一个小点:
在保存KVO信息的模型中,关于观察者observer属性的声明使用的是weak关键字,各位知道是为什么吗?这是因为如果不使用weak将会导致循环引用。
现在来分析:
kvoInfo持有了observer,保存kvo信息的数组持有了本次观察的kvoInfo,而self(即被观察对象)又持有了保存kvo信息的数组,这就相当于是被观察对象持有了观察者。在本例中,被观察对象是person,观察者是viewController,很显然,person是vc中的属性,所以观察者又持有了被观察对象,这就导致循环引用了。为了避免循环引用,那么就打断其中一条腿,所以在保存KVO信息的模型中,关于观察者observer属性的声明使用的是weak关键字。
KVO的自动移除
首先我们需要考虑的点是,什么时候去移除KVO的观察?
实际上,移除KVO的时间点应该是被观察对象销毁的时候。
那我就会想到,既然这样,我在NSObject+NormanKVO中复写dealloc方法好了:
这样做是有问题的,为什么呢?因为这是在NSObject的分类中直接覆写的dealloc,那么所有的直接或者间接继承自NSObject的类的实例销毁的时候都会走到这里,而我只需要在被KVO观测的对象销毁的时候走,所以这么写是不可以的。
然后我就想,既然不能直接复写系统的dealloc,那么我就方法交换呗。此时一定要注意交换的时机,不能在+load方法里面交换,因为这样的话,就又回到上面的问题了,所有的直接或者间接继承自NSObject的类的实例销毁的时候都会走到这里。那么我应该在什么时机去交换呢?
我应该在创建中间子类的时候,对该子类的dealloc方法进行方法交换。
此时,时机是没有问题了,但是这样写是有问题的。你想想,我中间子类没有实现dealloc,那么我是不是要到中间子类的父类也就是最初始原类中去查找dealloc?如果最初始原类中也没有复写dealloc,那么就会一层层网上找,一直找到NSObject中,然后将NSObject中的dealloc方法与中间子类中的自定义norman_dealloc方法进行交换,此时影响的就是所有的NSObject及其子类的实例对象了,那肯定是不可取的!
不知道大家还有没有印象,在上篇文章中,我在打印系统的KVO产生的中间类的方法列表的时候,是有4个方法,如下:
其中一个方法就是dealloc,这说明在中间子类中dealloc也被覆写了。
因此,基于上面的这些分析,我们应该可以猜测KVO观察自动移除的实际和地方了:
在中间子类创建的时候复写dealloc方法,然后在复写的dealloc中做移除观察者相关的操作(最重要的一步就是将isa重新指回来)。
代码修改如下:
这样的话,每当被监测对象销毁的时候,都会自动回调到norman_dealloc中,进而进行KVO监测的自动销毁。此时就不需要我们程序员手动在相关的dealloc中去移除观察者了!
最终代码如下。
记录KVO监测信息的模型NormanKVOInfo:
typedef NS_OPTIONS(NSUInteger, NormanKeyValueObservingOptions) {
NormanKeyValueObservingOptionNew = 0x01,
NormanKeyValueObservingOptionOld = 0x02,
};
typedef void(^NormanKVOHandleBlock)(id observer, NSString *keyPath, id oldValue, id newValue);
@interface NormanKVOInfo : NSObject
@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, assign) NormanKeyValueObservingOptions options;
@property (nonatomic, copy) NormanKVOHandleBlock handleBlock;
- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NormanKeyValueObservingOptions)options handleBolck:(NormanKVOHandleBlock)handleBlock;
@end
@implementation NormanKVOInfo
- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NormanKeyValueObservingOptions)options handleBolck:(NormanKVOHandleBlock)handleBlock {
if (self = [super init]) {
self.observer = observer;
self.keyPath = keyPath;
self.options = options;
self.handleBlock = handleBlock;
}
return self;
}
@end
KVO类目的实现:
@interface NSObject (NormanKVO)
// 添加KVO观察者
- (void)norman_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NormanKeyValueObservingOptions)options context:(nullable void *)context handle:(NormanKVOHandleBlock)handle;
@end
static NSString *const kNormanKVOPrefix = @"NormanKVONotifying_";
static NSString *const kNormanKVOAssiociateKey = @"kNormanKVOAssiociateKey";
@implementation NSObject (NormanKVO)
- (void)norman_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NormanKeyValueObservingOptions)options context:(void *)context handle:(NormanKVOHandleBlock)handle {
// 1,验证是否存在setter(保证实例变量不会进来)
[self judgeSetterMethodFromKeyPath:keyPath];
// 2,动态生成子类
Class newClass = [self createChildClassWithKeyPath:keyPath];
// 3,修改isa指向(isa-swizzling)
object_setClass(self, newClass);
// 4,保存观察者信息
[self saveKVOInfoWithObserver:observer keyPath:keyPath options:options handleBlock:handle];
}
#pragma mark - Private Methods
#pragma mark -- 验证是否存在setter方法
// 验证是否存在setter方法
- (void)judgeSetterMethodFromKeyPath:(NSString *)keyPath{
Class superClass = object_getClass(self);
SEL setterSeletor = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getInstanceMethod(superClass, setterSeletor);
if (!setterMethod) {
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"老铁没有当前%@的setter",keyPath] userInfo:nil];
}
}
// 从get方法获取set方法的名称 key ===>>> setKey:
static NSString *setterForGetter(NSString *getter) {
if (getter.length <= 0) { return nil;}
NSString *firstString = [[getter substringToIndex:1] uppercaseString];
NSString *leaveString = [getter substringFromIndex:1];
return [NSString stringWithFormat:@"set%@%@:",firstString,leaveString];
}
// 从set方法获取getter方法的名称 set<Key>:===> key
static NSString *getterForSetter(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 *firstString = [[getter substringToIndex:1] lowercaseString];
return [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstString];
}
#pragma mark -- 动态生成子类
- (Class)createChildClassWithKeyPath:(NSString *)keyPath {
NSString *oldClassName = NSStringFromClass([self class]);
NSString *newClassName = [NSString stringWithFormat:@"%@%@", kNormanKVOPrefix, oldClassName];
Class newClass = NSClassFromString(newClassName);
// 如果该中间子类不存在,则创建生成新类并注册
if (!newClass) {
/**
* 如果该中间子类在内存中不存在,则创建生成新类,需要传如下三个参数:
* 参数一:父类
* 参数二:类的名字
* 参数三:新类的开辟的额外空间
*/
// 2.1 : 申请类
newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
// 2.2 : 注册类
objc_registerClassPair(newClass);
// 2.3.1 : 给新类添加class方法 : 新类中的class指向的是新类的父类,也就是最初的原类
SEL classSEL = NSSelectorFromString(@"class");
Method classMethod = class_getInstanceMethod([self class], classSEL);
const char *classTypes = method_getTypeEncoding(classMethod);
class_addMethod(newClass, classSEL, (IMP)norman_class, classTypes);
// 2.3.2 : 给新类添加dealloc方法
SEL deallocSEL = NSSelectorFromString(@"dealloc");
Method deallocMethod = class_getInstanceMethod([self class], deallocSEL);
const char *deallocTypes = method_getTypeEncoding(deallocMethod);
class_addMethod(newClass, deallocSEL, (IMP)norman_dealloc, deallocTypes);
}
// 2.3.3 : 给新类添加setter方法
SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getInstanceMethod([self class], setterSEL);
const char *setterTypes = method_getTypeEncoding(setterMethod);
class_addMethod(newClass, setterSEL, (IMP)norman_setter, setterTypes);
return newClass;
}
Class norman_class(id self,SEL _cmd) {
return class_getSuperclass(object_getClass(self));
}
static void norman_dealloc(id self,SEL _cmd) {
NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kNormanKVOAssiociateKey));
if (observerArr.count<=0) {
return;
}
for (NormanKVOInfo *info in observerArr) {
[observerArr removeObject:info];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kNormanKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
break;
}
if (observerArr.count<=0) {
// 将isa指针指回中间子类的父类
Class superClass = [self class];
object_setClass(self, superClass);
}
}
static void norman_setter(id self, SEL _cmd, id newValue) {
// <1>,将setter消息转发给父类
void (*norman_msgSendSuper)(void *, SEL, id) = (void *)objc_msgSendSuper;
// void /* struct objc_super *super, SEL op, ... */
struct objc_super superStruct = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self)),
};
norman_msgSendSuper(&superStruct, _cmd, newValue);
// <2>,值发生变化之后,进行相关KVO的处理
// <2.1> : 拿到观察者
NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kNormanKVOAssiociateKey));
NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
id oldValue = [self valueForKey:keyPath];
for (NormanKVOInfo *info in observerArr) {
if ([info.keyPath isEqualToString:keyPath]) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSMutableDictionary<NSKeyValueChangeKey,id> *change = [NSMutableDictionary dictionaryWithCapacity:1];
// 对新、旧值进行处理
if (info.options & NormanKeyValueObservingOptionNew) {
[change setObject:newValue forKey:NSKeyValueChangeNewKey];
}
if (info.options & NormanKeyValueObservingOptionOld) {
[change setObject:@"" forKey:NSKeyValueChangeOldKey];
if (oldValue) {
[change setObject:oldValue forKey:NSKeyValueChangeOldKey];
}
}
// <2.2> : 将属性值变化的消息发送给观察者,以使观察者进行响应
!info.handleBlock ?: info.handleBlock(info.observer, info.keyPath, oldValue, newValue);
});
}
}
}
#pragma mark -- 保存观察者信息
- (void)saveKVOInfoWithObserver:(NSObject *)observer keyPath:(NSString *)keyPath options:(NormanKeyValueObservingOptions)options handleBlock:(NormanKVOHandleBlock)handleBlock {
// 4.1 : 获取存储观察者信息的数组
NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kNormanKVOAssiociateKey));
if (!observerArr) {
// 4.2 : 如果存储观察者信息的数组不存在,就新建一个
observerArr = [NSMutableArray arrayWithCapacity:1];
}
// 4.3 : 将观察者信息封装成一个数据模型,并存放在数组中
NormanKVOInfo *info = [[NormanKVOInfo alloc] initWitObserver:observer forKeyPath:keyPath options:options handleBolck:handleBlock];
[observerArr addObject:info];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kNormanKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
结语
我们使用了两篇文章的篇幅来介绍了KVO的使用、KVO的原理以及KVO的自定义,相信到这里诸位对KVO已经有了一个相对比较深入的了解了。
虽然我在上面列出了自定义KVO的所有代码,但是实际上这个自定义KVO还是非常简陋的,甚至可以说是漏洞百出的,不过架子是对的,整个关于KVO自定义的思路是没有任何问题的。
这里给大家推荐一个脸书开源的KVO工具——FBKVOController,如果大家觉得苹果官方提供的KVO太难用的话,可以使用FBKVOController,好用滴很~而且实现也很完善,没啥硬伤~
我们知道,KVO 在Foundation框架下,而Foundation的源码是没有开源的,所以我们找不到KVO的源码。但是我们退而求其次,我们可以在GNUStep的源码(http://www.gnustep.org/resources/downloads.php)中去探寻。
下载了相关源码之后打开工程全局搜索“addobserver”即可。
关于到底是什么,以及其与苹果的关系,大家可以自行百度。
以上。