自己实现KVO(block)

作者: 晨曦中的花豹 | 来源:发表于2021-07-13 19:17 被阅读0次

    先向先驱者致敬[https://tech.glowing.com/cn/implement-kvo/]

    看了我之前的仿系统实现KVO自己实现KVO(代理)的同学,看这个应该不会太费劲,但是其中有几个优化点和注意的问题

    • 是考虑多对象监听同一个对象向(不能创建多个子类,一监听的属性不能重复关联方法)
    • 考虑多个对象监听同一个类的不同对象
    • 移除监听时有个小插曲(就是用weak修饰的observer弱引用被提前置为nil,导致无法对比移除对应监听)

    下面是我在前者之上做了具体的分析
    包含两部分kvo_block分类LXCObservationInfo block保存对象
    kvo_block分类

    //
    //  NSObject+kvo_block.m
    //  leetCode
    //
    //  Created by 刘晓晨 on 2021/7/13.
    //
    
    #import "NSObject+kvo_block.h"
    
    static NSString *kvo_class_prefix = @"KVOClass_";
    const void *kvo_observers = &kvo_observers;
    
    @implementation NSObject (kvo_block)
    
    - (void)lxc_addObserver:(NSObject *)observer forKey:(NSString *)key block:(AddObserverBlock)block {
        
        // 1.判断属性是否存在
        SEL setterSelector = NSSelectorFromString(setterFromGetter(key));
        Method setterMethod = class_getInstanceMethod([self class], setterSelector);
        if (!setterMethod) {
            // 报异常,方便查找问题
            NSString *reason = [NSString stringWithFormat:@"%@没有%@对应的setter", self, key];
            @throw [NSException exceptionWithName:NSInvalidArgumentException
                                           reason:reason
                                         userInfo:nil];
        }
        
        //原始类
        Class oldClass = object_getClass(self);
        // 拿到原始类名
        NSString *oldClassName = NSStringFromClass(oldClass);
        
        //这里的判断主要是为了判断当前被监听对象是否已经被监听过,如果有被监听,则class名称带kvo前缀,不用重新创建子类,使用当前类即可
        if (![oldClassName hasPrefix:kvo_class_prefix]) {
            Class kvoClass =  [self makeKvoClassWithOriginalClassName:oldClassName];
            // 3.修改isa指针,使得self->isa指向子类
            object_setClass(self, kvoClass);
        }
        
        //判断是否属性关联到kvo_setter上,比如name,第一次监听name需要将sel与kvo_setter关联上,而第二次监听则不需要再次关联,但是实际发现重复添加并无问题
        IMP imp = class_getMethodImplementation(object_getClass(self), setterSelector);
        if (imp != (IMP)kvo_setter) {
            const char *types = method_getTypeEncoding(setterMethod);
            class_addMethod(object_getClass(self), setterSelector, (IMP)kvo_setter, types);
        }
        
        // 5.保存block及用于筛选的参数key和observer(这两个参数主要是在remove时候筛选使用)
        LXCObservationInfo *info = [[LXCObservationInfo alloc] init];
        info.block = block;
        info.key = key;
        info.observer = observer;
        
        
        //这里创建数组是因为同一个对象可能会被多个地方监听,这个时候需要去执行多个block
        NSMutableArray *observers = objc_getAssociatedObject(self, kvo_observers);
        if (!observers) {
            //创建保存监听对象的数组
            observers = [NSMutableArray array];
            objc_setAssociatedObject(self, kvo_observers, observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
        [observers addObject:info];
    }
    
    -(void)lxc_removeObserver:(NSObject *)observer forKey:(NSString *)key {
        NSMutableArray *observers = objc_getAssociatedObject(self, kvo_observers);
        for (LXCObservationInfo *observerInfo in observers) {
            /*
             这里判断如果监听对象一致,监听属性一致,及删除
             此处遇到了一个颠覆我认知的一个问题如果在info中我用weak来修饰observer,这个时候这里observerInfo.observer是获取不到的,因为调用这个方法一般都是在dealloc中,所以我认为所有指向自己的弱引用指针被置为nil是发生在dealloc之前,是不是很不可思议,但是实践证明确实如此,所以在info中我改为用assign修饰,因为此时外部传入的observer是有值的,所以assign修饰不会出问题
             */
            if (observerInfo.observer == observer && [observerInfo.key isEqualToString:key]) {
                [observers removeObject:observerInfo];
                break;
            }
        }
    }
    
    //创建子类
    - (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_setter(id self, SEL _cmd, id newName)
    {
        
        NSString *setterName = NSStringFromSelector(_cmd);
        NSString *getterName = getterFromSetter(setterName);
        
        id  oldValue;
        if (getterName) {
            oldValue = [self valueForKey:getterName];
        }
        // 获取kvo类型
        id class = object_getClass(self);
        
        // 调用父类的方法(此处还有一种方式是修改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 = [self valueForKey:getterName];
        
        NSMutableArray *observers = objc_getAssociatedObject(self, kvo_observers);
        for (LXCObservationInfo *observer in observers) {
            if ([observer.key isEqualToString:getterName]) {
                observer.block(oldValue, newValue);
            }
        }
    }
    
    //通过属性获取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
    
    

    LXCObservationInfo block保存对象

    //
    //  LXCObservationInfo.h
    //  leetCode
    //
    //  Created by 刘晓晨 on 2021/7/9.
    //
    
    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    typedef void(^AddObserverBlock)(id oldValue, id newValue);
    
    @interface LXCObservationInfo : NSObject
    
    @property (nonatomic,assign)id observer;
    @property (nonatomic,copy)NSString *key;
    @property (nonatomic,copy)AddObserverBlock block;
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    

    最后是小插曲的验证
    A弱引用B,B实现block(其中打印A弱引用的B),在B的dealloc中调用block,发现block执行,A弱引用的B竟然是nil

    A中

    @property (nonatomic,weak)TwoViewController *two;
    
    self.two = vc;
    __weak typeof(self) weakSelf = self;
    vc.block = ^{
            NSLog(@"twoblock%@",weakSelf.two);
    };
    

    B中

    -(void)dealloc {
        self.block();
    }
    

    最终打印

    2021-07-13 18:52:28.715064+0800 leetCode[74996:13935331] twoblock(null)
    

    ok关于KVO的内容到此结束,因为weak引用的问题,之后我会更新一版weak的知识点,希望大家多多支持

    相关文章

      网友评论

        本文标题:自己实现KVO(block)

        本文链接:https://www.haomeiwen.com/subject/fjvspltx.html