和谐学习!不急不躁!!我是你们的老朋友小青龙~
前言
前面文章 探究分析了KVO的原理,本篇文章就原理内容进行一番KVO的自定义。首先结合原理梳理下我们自定义要做的事情:
-
是否拥有set方法(
KVO本质上是对set方法的观察
) -
动态生成一个子类,并添加set方法
-
原类的实例变量的isa指向子类
-
set方法被调用,发起通知
-
remove之后,修改实例变量的isa指向原类
核心方法
添加观察模式
/// 添加观察模式:
- (void)ssj_addObserver:(id)observer forKeyPath:(NSString *)keyPath observerType:(ObserverType)observerType context:(NSString *)context{
/// 先判断是否拥有set方法,(「成员变量」这种是不能被KVO观察到的,在KVO原理篇章我们已经测试过了)
[self hasSetterMethod:keyPath];
/// 记录这条观察数据
[self addKVOInfo:observer keyPath:keyPath context:context observerType:observerType];
/// 创建 SSJNSKVONotifying_xxx 子类
Class subCls = [self ssj_createSubClassFromOriginClass:nil forKeyPath:keyPath];
/// 实例变量的isa指向子类
object_setClass(self, subCls);
}
复制代码
移除指定观察模式:
/// 移除指定观察模式
/// @param observer 观察者
/// @param keyPath 观察的key路径
- (void)ssj_removeObserver:(id)observer forKeyPath:(NSString *)keyPath{
/// 1、从kVOInfos移除这条kVOInfo记录
[self removeKVOInfo:observer keyPath:keyPath context:NULL];
/// 2、实例变量的isa指向原类
NSArray *kVOInfos = objc_getAssociatedObject(self, SSJKVOInfoKey);
if(kVOInfos.count == 0){
/// kVOInfos.count为空表示,当前已经移除了所有的观察模式,那么实例对象的「isa」就可以指向「原类」
/// [self class] 获取的是原类
object_setClass(self, [self class]);
}
}
复制代码
通知回调(需要观察者实现):
/// 通知回调
/// 当值发生变化时,通知到这个方法
- (void)ssj_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
/// 需要观察者重写这个方法
}
复制代码
动态生成一个子类
- (Class)ssj_createSubClassFromOriginClass:(Class)originClass forKeyPath:(NSString *)keyPath{
NSString *originClassName = NSStringFromClass([self class]);
NSString *ssj_subClassName = [@"SSJNSKVONotifying_" stringByAppendingString:originClassName];
const char *ssj_subClassName_char = ssj_subClassName.UTF8String;
Class class_new = objc_getClass(ssj_subClassName_char);
// 判断是否存在这个子类,不存在就创建
if(!class_new){
NSLog(@"开始创建SSJNSKVONotifying_子类~");
/// 申请类
class_new = objc_allocateClassPair([self class], ssj_subClassName_char, 0);
/// 注册类
objc_registerClassPair(class_new);
/// 添加方法 (把「原类」的所有「实例方法」都加入到「SSJNSKVONotifying_子类」)
SEL sel_class = NSSelectorFromString(@"class");
Method md = class_getInstanceMethod([self class], sel_class);
const char*type = method_getTypeEncoding(md);
class_addMethod(class_new, sel_class, (IMP)ssj_class, type);
/// 添加keypath对应setter方法
if (keyPath.length > 0) {
/// setKeypath
NSString *method_setName = [NSString stringWithFormat:@"set%@:",[self smallToBigWithFirstCharFrom:keyPath]];
SEL sel_keyPath = NSSelectorFromString(method_setName);
Method md_keyPath = class_getInstanceMethod([self class], sel_keyPath);
const char*type_keyPath = method_getTypeEncoding(md_keyPath);
class_addMethod(class_new, sel_keyPath, (IMP)ssj_keypathMethod, type_keyPath);
}
}
return class_new;
}
复制代码
keypath 的 setter方法
#pragma mark - setter方法
static void ssj_keypathMethod(id self,SEL _cmd,id value_new){
/// 获取旧值
NSString *old_key = getterForSetter(NSStringFromSelector(_cmd));
NSString *old_Value = nil;
if (old_key && old_key.length > 0) {
old_Value = [self valueForKey:old_key];
}
///开始调用父类的set:value:方法
struct objc_super obj_super_class = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self)),
};
/// 将objc_msgSendSuper转换成对应的函数指针
int (*ssj_msgSendSuper)(void *, SEL, id) = (void *)objc_msgSendSuper;
/// 调用ssj_msgSendSuper方法,&操作符是c/c++取指针的操作符
ssj_msgSendSuper(&obj_super_class, _cmd, value_new); // 这里的返回值类型根据实际调用方法可知,是一个int类型
/// 打印新值、旧值
NSLog(@"SSJNSKVONotifying_XXX 监听到set方法--旧值:%@ - 新值:%@",old_Value,value_new);
/// changeDic包含了返回的值(新值、旧值等,具体看observerType)
NSMutableDictionary *changeDic = [NSMutableDictionary new];
id obser = nil;
NSArray *kVOInfos = objc_getAssociatedObject(self, SSJKVOInfoKey);
/// 通过objc_getAssociatedObject获取的数组是Array类型,这里转化成NSMutableArray类型
NSMutableArray *kVOInfos_new = [kVOInfos mutableCopy];
for (SSJKVOInfo *kVOInfo in kVOInfos) {
/// 判断keyPath匹配
BOOL keyPathCompare = [kVOInfo.keyPath isEqualToString:old_key];
/// 匹配需要返回的数据有哪些
if (kVOInfo.observerType == ObserverTypeOfNewAndOld) {
if (old_Value) {
changeDic = [@{
@"value_old":old_Value,
@"value_new":value_new,
} mutableCopy];
}else{
changeDic = [@{
@"value_new":value_new,
} mutableCopy];
}
}else if (kVOInfo.observerType == ObserverTypeOfNew) {
changeDic = [@{
@"value_new":value_new,
} mutableCopy];
}else if (kVOInfo.observerType == ObserverTypeOfOld) {
changeDic = [@{
@"value_old":old_Value,
} mutableCopy];
}
/// 匹配成功,发起回调
if (keyPathCompare) {
obser = kVOInfo.observer;
if(!obser) {
///说明observer被释放了,但是kVOInfos里还保存着这条数据,此处应该删除这条垃圾数据
[kVOInfos_new removeObject:kVOInfo];
continue;//忽略obser为空的情况
}
/// 给观察者发送回调
SEL ssj_obser_sel = @selector(ssj_observeValueForKeyPath:ofObject:change:context:);
objc_msgSend(obser,ssj_obser_sel,old_key,self,changeDic,kVOInfo.context);
}
}
/// 重写赋值(此刻的kVOInfo已经去掉了obser是为nil的数据记录)
objc_setAssociatedObject(self, SSJKVOInfoKey, kVOInfos_new, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
复制代码
辅助方法、常量字符串
常量字符串
static const void *SSJKVOInfoKey = &SSJKVOInfoKey;
static NSString const* SSJClassHead = @"SSJNSKVONotifying_";
复制代码
是否拥有set方法
/// 是否拥有set方法
- (void)hasSetterMethod:(NSString *)keyPath{
/// 获得完整的方法名 setXyy
NSString *method_setName = [NSString stringWithFormat:@"set%@:",[self smallToBigWithFirstCharFrom:keyPath]];
SEL sel_class = NSSelectorFromString(method_setName);
Method md = class_getInstanceMethod([self class], sel_class);
NSString *showMsg = [NSString stringWithFormat:@"观察模式添加失败,因为%@不是属性",keyPath];
NSAssert(md, showMsg);
}
复制代码
返回父类信息
/// 返回父类信息
Class ssj_class(id self,SEL _cmd){
return class_getSuperclass(object_getClass(self));
}
复制代码
从set方法获取getter方法的名称
/// @"setName:" -> @"name"
static inline NSString *getterForSetter(NSString *setter){
NSString *strBegin = [setter substringToIndex:3];
NSString *strEnd = [setter substringFromIndex:setter.length-1];
/// “set”开头,“:”结尾
if ([strBegin isEqualToString:@"set"] && [strEnd isEqualToString:@":"]) {
///去掉"set"和":"
NSString *string_return = [setter substringFromIndex:3];
string_return = [string_return substringToIndex:string_return.length-1];
/// 将第一个字符转为小写
string_return = bigToSmallWithFirstCharFrom(string_return);
return string_return;
}
return nil;
}
复制代码
第一个字母转化为大写
/// 将参数string第一个字母转化为大写
/// @param string 需要处理的字符
/// return 返回处理好的字符串
- (NSString *)smallToBigWithFirstCharFrom:(NSString *)string{
if (string) {
if (string.length == 1) {
return string.uppercaseString;///转化大写
}else if (string.length > 1) {
NSString *firstChar = [string substringToIndex:1];
NSString *atferDeal = firstChar.uppercaseString;///转化大写
NSString *endStr = [string substringFromIndex:1];
return [NSString stringWithFormat:@"%@%@",atferDeal,endStr];
}else{
return @"";
}
}
NSLog(@"参数string为空,转化失败~");
return nil;
}
复制代码
第一个字母转化为小写
/// 将参数string第一个字母转化为小写
/// @param string 需要处理的字符
/// return 返回处理好的字符串
static inline NSString *bigToSmallWithFirstCharFrom(NSString *string){
if (string) {
if (string.length == 1) {
return string.lowercaseString;///转化小写
}else if (string.length > 1) {
NSString *firstChar = [string substringToIndex:1];
NSString *atferDeal = firstChar.lowercaseString;///转化小写
NSString *endStr = [string substringFromIndex:1];
return [NSString stringWithFormat:@"%@%@",atferDeal,endStr];
}else{
return @"";
}
}
NSLog(@"参数string为空,转化失败~");
return nil;
}
复制代码
添加kVOInfos管理
/** kVOInfos是给NSObject拓展的属性,类型是NSMutableArray <SSJKVOInfo *>*,
(注意:通过objc_setAssociatedObject获取的类型是不可变NSArray)
作用:记录被观察者的观察信息,「ssj_addObserver:」或「ssj_removeObserver:」以及「ssj_keypathMethod」都有可能让kVOInfos发生变化,具体看代码。
由于「SSJKVOInfo」的「observer」属性是「weak」修饰的,它会跟着观察者的销毁而变成nil;
「ssj_keypathMethod」作为「keypath」的set方法,在给观察者发消息之前,会先判断这个观察者是否存在,所以即便没有调用「ssj_removeObserver:」,也不会影响下一次使用。
*/
/// 手动添加kVOInfos - get方法
- (NSMutableArray<SSJKVOInfo *> *)kVOInfos{
return objc_getAssociatedObject(self, SSJKVOInfoKey);
}
/// 手动添加kVOInfos - set方法
- (void)setKVOInfos:(NSMutableArray<SSJKVOInfo *> *)kVOInfos {
objc_setAssociatedObject(self, SSJKVOInfoKey, kVOInfos, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
/// 添加 - 新的kVOInfo
/// @param observer 观察者
/// @param keyPath 观察的key路径
/// @param context 上下文
- (BOOL)addKVOInfo:(id)observer keyPath:(NSString *)keyPath context:(NSString *)context observerType:(ObserverType)observerType{
NSArray *kVOInfos = objc_getAssociatedObject(self, SSJKVOInfoKey);
NSMutableArray *kVOInfos_new = [kVOInfos mutableCopy];
for (SSJKVOInfo *kVOInfo in kVOInfos) {
BOOL observerCompare = kVOInfo.observer == observer;
BOOL keyPathCompare = [kVOInfo.keyPath isEqualToString:keyPath];
if (observerCompare && keyPathCompare) {
/// 说明已经存在这个监听
return NO;
}
}
SSJKVOInfo *kVOInfo_new = [SSJKVOInfo new];
kVOInfo_new.observer = observer;
kVOInfo_new.keyPath = keyPath;
kVOInfo_new.context = context;
kVOInfo_new.observerType = observerType;
if (!kVOInfos_new) {
kVOInfos_new = [NSMutableArray new];
}
[kVOInfos_new addObject:kVOInfo_new];
self.kVOInfos = kVOInfos_new;
return YES;
}
/// 移除 - 指定kVOInfo
/// @param observer 观察者
/// @param keyPath 观察的key路径
/// @param context 上下文,在这里基本上没啥哟过
- (void)removeKVOInfo:(id)observer keyPath:(NSString *)keyPath context:(nullable void *)context{
NSMutableArray *kVOInfos_new = [self.kVOInfos mutableCopy];
for (SSJKVOInfo *kVOInfo in self.kVOInfos) {
BOOL observerCompare = kVOInfo.observer == observer;
BOOL keyPathCompare = [kVOInfo.keyPath isEqualToString:keyPath];
if (observerCompare && keyPathCompare) {
/// 说明已经存在这个监听
[kVOInfos_new removeObject:kVOInfo];
continue;
}
/// 因为observer是weak修饰,如果观察者不存在了,那么observer就变成nil
/// 这时候,这条SSJKVOInfo数据就变成脏数据,也需要清理
if(!kVOInfo.observer){
[kVOInfos_new removeObject:kVOInfo];
}
}
self.kVOInfos = kVOInfos_new;
}
复制代码
如何使用
- (void)viewDidLoad {
[super viewDidLoad];
SSJPerson *person = [SSJPerson shareSingleSSJPerson];
person.name = @"张三";
[person ssj_addObserver:self forKeyPath:@"name" observerType:ObserverTypeOfNewAndOld context:@"SSJPerson_name_Context"];
person.name = @"李四";
NSString *str = person.name;
NSLog(@"%s 查看当前name:%@",__func__,str);
}
/// 通知回调
- (void)ssj_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(NSString *)context{
NSLog(@"回调~~~%@",change);
}
复制代码
运行效果:
此刻isa还是指向中间类:
remove之后isa指向原类:
优化
到目前,我们知道调用自定义KVO需要三步曲:
-
ssj_addObserver:forKeyPath:observerType:context:
-
ssj_observeValueForKeyPath:ofObject:change:context:
-
ssj_removeObserver: forKeyPath:
那么,有没有办法自动管理ssj_removeObserver
呢?如果是你,你会有什么好的方案呢?
没错,我想到了方法交换
。
通常来说,我们会把方法交换放在+ (void)load
,这样显然是不行的,因为我们不需要拦截所有的「NSObject」类的dealloc
方法。具体实现:
/// 添加观察模式
- (Class)ssj_createSubClassFromOriginClass:(Class)originClass forKeyPath:(NSString *)keyPath{
...
/// 写在这里而不是写在load方法里,只对调用了ssj_addObserver的观察者起作用
Method originalMethod = class_getInstanceMethod([self class], NSSelectorFromString(@"dealloc"));
Method swizzlingMethod = class_getInstanceMethod([self class], @selector(ssj_dealloc));
method_exchangeImplementations(originalMethod, swizzlingMethod);
}
- (void)ssj_dealloc{
NSLog(@"%@的dealloc 我拦截到了~~~dealloc",NSStringFromClass([self class]));
/// 实例变量的isa指向原类
object_setClass(self, [self class]);
/// 清除记录
self.kVOInfos = [NSMutableArray new];
}
复制代码
运行:
看起来没啥问题,但经测试发现:
-
观察者设置为ViewContor类(及派生类),无论观察者是否实现
dealloc
,这样都是可以的。 -
当观察者设置为数据模型本身,并且没有实现dealloc方法的时候,就会找不到dealloc地址。
SSJPerson
类观察自己weight
属性的变化,并且没有实现dealloc
方法:
代码进行修改:
/// 添加观察模式
- (Class)ssj_createSubClassFromOriginClass:(Class)originClass forKeyPath:(NSString *)keyPath{
...
/// 为中间类添加dealloc方法,imp指向ssj_dealloc
Method originalMethod = class_getInstanceMethod([self class], NSSelectorFromString(@"dealloc"));
const char * types = method_getTypeEncoding(originalMethod);
class_addMethod(class_new, NSSelectorFromString(@"dealloc"), (IMP)ssj_dealloc,types);
}
static void ssj_dealloc(id self,SEL _cmd){
NSLog(@"%@的dealloc 我拦截到了~~~dealloc",NSStringFromClass([self class]));
/// 实例变量的isa指向原类
object_setClass(self, [self class]);
/// 清除记录
objc_setAssociatedObject(self, SSJKVOInfoKey, [NSMutableArray new], OBJC_ASSOCIATION_COPY_NONATOMIC);
}
复制代码
运行:
至此,完成了自动销毁的功能。
代码
密码:547z
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END