美文网首页
KVO的底层原理和自实现KVO

KVO的底层原理和自实现KVO

作者: 强子_4216 | 来源:发表于2018-07-27 14:34 被阅读0次

    一、KVO的概述

    KVO : Key-Value Observing(观察者设计模式)

    当被观察对象的值改变时,观察者就会受到通知,这种模式有利于两个类间的解耦合,尤其是对于业务逻辑与视图控制 这两个功能的解耦合。

    二、KVO的实现原理

    • 1、KVO是基于runtime机制实现的;
    • 2、当某个类(Person)的属性被观察时,系统会自动的派生一个该类的子类(NSKVONotifying_Person)
    • 3、为该子类添加setter方法;
    • 4、消息转发,子类(NSKVONotifying_Person)--> 父类(Person);
    • 备注:苹果粑粑还悄悄的重写了该类的class方法,从而让我们感知不到子类的存在;

    三、自实现KVO(观察者模式)

    首先看下系统的KVO

    //给self.Person属性添加观察者   
    [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
    
    //通知观察者
    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
        if ([keyPath isEqualToString:@"name"]) {
            NSLog(@"通知了观察者,被观察的值改变了");
        }
    }
    

    下面我们自己实现KVO
    话不多说,代码撸起 Demo

    //NSObject+CGQKVO.h
    
    #import  <Foundation/Foundation.h>
    #import <objc/message.h>
    
    typedef void (^QZKVOBlock)(id observer, id keyPath, id newValue, id oldValue);
    @interface NSObject (CGQKVO)
    
    /**
    添加观察者
    @param observer 观察者对象
    @param keyPath 需要观察的键值
    @param handle 回调block
    */
    - (void)qz_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath withHandle:(QZKVOBlock)handle;
    
    /**
    移除观察者
    @param observer 观察值对象
    @param keyPath 观察的键值
    */
    -(void)qz_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
    @end
    
    
    //创建一个info类来保存观察的数据
    @interface QZ_Info : NSObject
    @property (nonatomic, weak) id observer;
    @property (nonatomic,copy) NSString *keyPath;
    @property (nonatomic,copy) QZKVOBlock handle;
    -(instancetype)initWhit:(id)observer keyPath:(NSString *)keyPath handle:(QZKVOBlock)handle;
    @end
    
    @implementation QZ_Info
    -(instancetype)initWhit:(id)observer keyPath:(NSString *)keyPath handle:(QZKVOBlock)handle{
        self = [super init];
        if (self) {
            _observer = observer;
            _keyPath = keyPath;
            _handle = handle;
        }
        return self;
    }
    
    @end
    
    //NSObject+CGQKVO.m
    
    #import "NSObject+CGQKVO.h"
    static NSString *const QZKVOPrefix = @"QZKVO_";
    static NSString *const QZKVOAssicationKey = @"QZKVOAssicationKey";
    
    @implementation NSObject (CGQKVO)
    - (void)qz_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath withHandle:(QZKVOBlock)handle{
    
        //检查是否是实例变量(实例变量就抛出异常)
        //1.获取当前类
        Class superClass = object_getClass(self);
        //2.检查是否有setter方法
        SEL setterSeletor = NSSelectorFromString(setterFormGetter(keyPath));
        Method setterMethod = class_getInstanceMethod(superClass, setterSeletor);
    
        if (!setterMethod) {
            @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"这个key:%@,没有setter:方法",keyPath] userInfo:nil];
        }
    
        //动态创建子类创建子类
        NSString *superClassName = NSStringFromClass(superClass);
        Class childClass;
        if (![superClassName hasPrefix:QZKVOPrefix]) {
            childClass = [self createClassFromSuperClass:superClassName];
            //把动态创建的子类指向父类
            //isa_swizzling-->黑魔法
            object_setClass(self, childClass);
        }
    
        //为子类添加setter方法
        /**
        class_addMethod(<#Class  _Nullable __unsafe_unretained cls#>, <#SEL  _Nonnull name#>, <#IMP  _Nonnull imp#>, <#const char * _Nullable types#>)
        1.class -->  给谁添加
        2.SEL  -->  方法编号
        3.IMP  -->  函数指针,指向函数实现
        4.types -->  返回值,参数
        */
        const char *types = method_getTypeEncoding(setterMethod);
        class_addMethod(childClass, setterSeletor, (IMP)QZKVO_Setter, types);
        
        //添加回调,方便调用者处理
        QZ_Info *info = [[QZ_Info alloc] initWhit:observer keyPath:keyPath handle:handle];
        NSMutableArray *Arr = objc_getAssociatedObject(self, &QZKVOAssicationKey);
        if (!Arr) {
            Arr = [[NSMutableArray alloc] initWithCapacity:1];
            objc_setAssociatedObject(self, &QZKVOAssicationKey, Arr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
        [Arr addObject:info];
    }
    
    -(void)qz_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
        NSMutableArray *Arr = objc_getAssociatedObject(self, &QZKVOAssicationKey);
        QZ_Info *tmp;
        for (QZ_Info *info in Arr) {
            if ([info.keyPath isEqualToString:keyPath]) {
                tmp = info;
                break;
            }
        }
        if (tmp) {
            [Arr removeObject:tmp];
        }
    }
    
    /**
    通过父类创建子类
    @param superClassName 父类类名
    @return 子类
    */
    - (Class)createClassFromSuperClass:(NSString *)superClassName{
    
        //获取父类
        Class superClass = object_getClass(self);
    
        //创建新类
        //1.获取子类类名
        NSString *childClassName = [QZKVOPrefix stringByAppendingString:NSStringFromClass(superClass)];
    
        //2.检查是否创建过该子类
        Class childClass = NSClassFromString(childClassName);
        //创建过就直接返回改类
        if (childClass) return childClass;
    
        //没创建过就开始创建
        /**
        1.superClass --> 父类
        2.childClassName --> 创建的类名
        3.size_t extraBytes -->开辟的内存空间
        */
        childClass = objc_allocateClassPair(superClass, childClassName.UTF8String, 0);
    
        /**
        添加子类动态添加的class方法
        class_addMethod(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name, IMP  _Nonnull imp, const char * _Nullable types)
        1.class --> 给谁添加
        2.SEL  --> 方法编号
        3.IMP  --> 函数指针,指向函数的实现
        4.types --> 返回值/参数
        */
        Method childClassMethod = class_getInstanceMethod(superClass, @selector(class));//获取父类的class方法
        const char *types = method_getTypeEncoding(childClassMethod);
        class_addMethod(childClass, @selector(class),(IMP)QZKVO_Class, types);
    
        //注册
        objc_registerClassPair(childClass);
    
        return childClass;
    }
    
    #pragma mark - 函数区域
    
    static void QZKVO_Setter(id self, SEL _cmd, id newValue){
        NSString *setterName = NSStringFromSelector(_cmd);
        NSString *getterName = getterFormSetter(setterName);
        if (!getterName) {
            @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%@没有getter:%@方法",self,getterName] userInfo:nil];
        }
    
        //取出旧值
        id oldValue = [self valueForKey:getterName];
    
        //即将改变值
        [self willChangeValueForKey:getterName];
    
        //消息转发 子类-->父类
        /**
        objc_msgSendSuper(void * struct objc_super *super, SEL op, ... *)
        1.super --> void * struct objc_super 结构体
        2.op    --> SEL方法编号
        */
        void (*qzkvo_megSendSuper)(void *, SEL, id) = (void*)objc_msgSendSuper;
    
        struct objc_super qz_objcSuper = {
            /// Specifies an instance of a class.
            .receiver = self,
            .super_class = class_getSuperclass(object_getClass(self)),
        };
    
        qzkvo_megSendSuper(&qz_objcSuper, _cmd, newValue);
    
        //已经改变值
        [self didChangeValueForKey:getterName];
    
        NSArray *Arr = objc_getAssociatedObject(self, &QZKVOAssicationKey);
        for (QZ_Info *info in Arr) {
            if ([info.keyPath isEqualToString:getterName]) {
                info.handle(info.observer, info.keyPath, newValue, oldValue);
            }
        }
    }
    
    static Class QZKVO_Class(id self){
        return class_getSuperclass(object_getClass(self));
    }
    
    //name --> setName:
    static NSString *setterFormGetter(NSString * getter){
    
        if (getter.length == 0)   return nil;
    
        NSString *firstString = [[getter substringToIndex:1] uppercaseString];
        NSString *lastString = [getter substringFromIndex:1];
        return [NSString stringWithFormat:@"set%@%@:",firstString,lastString];
    }
    
    //setName: --> name
    static NSString *getterFormSetter(NSString * setter){
    
        if (setter.length <= 0 || ![setter hasPrefix:@"set"] || ![setter hasSuffix:@":"]) {
            return nil;
        }
        NSRange range = NSMakeRange(3, setter.length-4);
        NSString *getterStr = [setter substringWithRange:range];
        NSString *firstString = [[getterStr substringToIndex:1] lowercaseString];
        return [getterStr stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstString];
    }
    
    @end
    
    

    总结

    KVO实际上就是观察对象的setter方法,对于没有setter方法对象和不调用setter方法(_name = @"张三")时,值的改变,是无法触发KVO机制的;
    KVO机制能够很方便的提供两个对象间的同步(像view和model之间的同步)
    自己完成实现KVO后,会对KVO底层有一个较深入的了解,增加了block回调也能增加代码的可读性和可维护性;

    相关文章

      网友评论

          本文标题:KVO的底层原理和自实现KVO

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