之前已经了解系统实现KVO的过程,这篇文章主要说一下自己实现KVO的思路
开始之前推荐大家看一下这篇文章,很多细节这个里面说的很清楚,并且实现了block进行回调,我这里只是为了模仿系统实现KVO,之后我也会更新Block的版本
https://tech.glowing.com/cn/implement-kvo/
下面是我的具体实现,及每一步的作用:
//
// NSObject+LXC_KVO.m
// leetCode
//
// Created by 刘晓晨 on 2021/7/9.
//
#import "NSObject+LXC_KVO.h"
#import
#import
#import "LXCObservationInfo.h"
static NSString *kvo_class_prefix = @"KVOClass_";
const void *kvo_observer = &kvo_observer;
const void *kvo_info = &kvo_info;
@implementation NSObject (LXC_KVO)
- (void)lxc_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context
{
//原始类
Class oldClass = object_getClass(self);
// 拿到原始类名
NSString *oldClassName = NSStringFromClass(oldClass);
//原始SEL
SEL oldSelector = NSSelectorFromString(setterFromGetter(keyPath));
// 1.创建子类
Class kvoClass = [self makeKvoClassWithOriginalClassName:oldClassName];
//2.重写set方法(本质是替换方法的实现)
// cls 添加新方法的类; name 表示selector的方法名称; imp 指向一个方法的实现; types 表示我们要添加方法的返回值和参数: v 代表函数的返回值类型 void, :@ 表示调用setName:函数的时的参数
Method clazzMethod = class_getInstanceMethod(oldClass, oldSelector);
const char *types = method_getTypeEncoding(clazzMethod);
class_addMethod(kvoClass, oldSelector, (IMP)kvo_setName, types);
// 3.修改isa指针,使得self->isa指向子类
object_setClass(self, kvoClass);
// 4.保存观察者对象(所谓的循环引用根源在此)
objc_setAssociatedObject(self, kvo_observer, observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// 5.保存上下文及options(系统如何保存目前不知道,希望知道的朋友指教)
LXCObservationInfo *info = [[LXCObservationInfo alloc] init];
info.context = context;
info.options = options;
objc_setAssociatedObject(self, kvo_info, info, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
//创建子类
- (Class)makeKvoClassWithOriginalClassName:(NSString *)originalClazzName {
NSString *kvoClazzName = [kvo_class_prefix stringByAppendingString:originalClazzName];
Class clazz = NSClassFromString(kvoClazzName);
if (clazz) {
return clazz;
}
Class originalClazz = object_getClass(self);
//让新的Class继承自原始类
Class kvoClazz = objc_allocateClassPair(originalClazz, kvoClazzName.UTF8String, 0);
//仿苹果隐藏子类(及重写本类的class对象方法)
Method clazzMethod = class_getInstanceMethod(originalClazz, @selector(class));
const char *types = method_getTypeEncoding(clazzMethod);
class_addMethod(kvoClazz, @selector(class), (IMP)kvo_class, types);
//注册子类
objc_registerClassPair(kvoClazz);
return kvoClazz;
}
//重写class方法
Class kvo_class(id self, SEL _cmd) {
return class_getSuperclass(object_getClass(self));
}
//统一管理setter方法
void kvo_setName(id self, SEL _cmd, id newName)
{
// 获取kvo类型
id class = object_getClass(self);
// 拿到观察者
id observer = objc_getAssociatedObject(self, kvo_observer);
//信息
LXCObservationInfo *info = objc_getAssociatedObject(self, kvo_info);
//判断是否监听变化之前
id oldValue;
if (info.options && NSKeyValueObservingOptionOld) {
//1.获取原值
oldValue = ((id (*)(id,SEL))objc_msgSend)(self, NSSelectorFromString(getterFromSetter(NSStringFromSelector(_cmd))));
}
// 调用父类的方法(此处还有一种方式是修改self isa 指向原始类,修改后在修改为 子类,这里使用的是系统实现super的方式,顺便可以了解下super和self的区别)
Class super_class = class_getSuperclass(class);
struct objc_super * _Nonnull super_struct = malloc(sizeof(struct objc_super));
super_struct->receiver = self;
super_struct->super_class = super_class;
objc_msgSendSuper(super_struct, _cmd,newName);
//判断需要返回哪些值
id newValue;
if (info.options && NSKeyValueObservingOptionNew) {
//1.获取修改值
newValue = ((id (*)(id,SEL))objc_msgSend)(self, NSSelectorFromString(getterFromSetter(NSStringFromSelector(_cmd))));
}
NSDictionary *dict = [[NSMutableDictionary alloc] init];
if (oldValue != nil) {
[dict setValue:oldValue forKey:NSKeyValueChangeOldKey];
}
if (newValue != nil) {
[dict setValue:newValue forKey:NSKeyValueChangeNewKey];
}
//2.发送给监听者
objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:),getterFromSetter(NSStringFromSelector(_cmd)),observer,dict,info.context);
}
//通过属性获取setter字符串
NSString* setterFromGetter(NSString *key) {
if (key.length > 0) {
NSString *resultString = [key stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:[[key substringToIndex:1] capitalizedString]];
return [NSString stringWithFormat:@"set%@:",resultString];
}
return nil;
}
//通过setter 获取getter
NSString* getterFromSetter(NSString *key) {
if (key.length > 0) {
NSString *resultString = [key substringFromIndex:3];
resultString = [resultString substringToIndex:resultString.length - 1];
return [resultString lowercaseString];
}
return nil;
}
@end
上面kvo_setName
中调用父类的setter方法使用的是系统提供的方法,这里还有一种可以通过修改isa去调用object_setClass
调用完后在重新指向子类
最后做下测试:
- (void)viewDidLoad {
[super viewDidLoad];
_person = [Person new];
_person.ageee = @"123";
NSLog(@"%p ---- %p",[_person class],object_getClassName(_person));
[_person lxc_addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:sizeContext];
NSLog(@"%p ---- %p",[_person class],object_getClassName(_person));
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == sizeContext) {
if ([keyPath isEqualToString:@"age"]) {
if (change[NSKeyValueChangeOldKey] != nil) {
NSLog(@"old = %@",change[NSKeyValueChangeOldKey]);
}
if (change[NSKeyValueChangeNewKey] != nil) {
NSLog(@"new = %@",change[NSKeyValueChangeNewKey]);
}
}
}
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
static int i = 0;
_person.age = [NSString stringWithFormat:@"%d",i++];
}
下一篇,准备参照开始说的文章,做一个block回调的KVO,感谢大家支持!!!