自己实现KVO(代理方式)

之前已经了解系统实现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,感谢大家支持!!!

你可能感兴趣的:(自己实现KVO(代理方式))