美文网首页
21.iOS底层学习之KVO自定义

21.iOS底层学习之KVO自定义

作者: 牛牛大王奥利给 | 来源:发表于2021-12-28 13:30 被阅读0次

    本篇文章提纲:
    1、自定义KVO
    2、函数式KVO
    3、KVO的自动销毁机制
    4、FBKVOController
    5、GNUStep Base

    自定义KVO

    我们仿照KVO的实现方式,自定义一个NSObecjt的一个分类LGKVO。

    • 自定义添加观察者lg_addObserver
    - (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LGKeyValueObservingOptions)options context:(nullable void *)context{
        
        // 1: 验证是否存在setter方法 : 不让实例进来
        [self judgeSetterMethodFromKeyPath:keyPath];
        // 2: 动态生成子类
        Class newClass = [self createChildClassWithKeyPath:keyPath];
        // 3: isa的指向 : LGKVONotifying_LGPerson
        object_setClass(self, newClass);
        // 4: 保存观察者信息
        LGKVOInfo *info = [[LGKVOInfo alloc] initWitObserver:observer forKeyPath:keyPath options:options];
        NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
        
        if (!observerArr) {
            observerArr = [NSMutableArray arrayWithCapacity:1];
            [observerArr addObject:info];
            objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
    
    }
    
    - (void)judgeSetterMethodFromKeyPath:(NSString *)keyPath{
        Class superClass    = object_getClass(self);
        SEL setterSeletor   = NSSelectorFromString(setterForGetter(keyPath));
        Method setterMethod = class_getInstanceMethod(superClass, setterSeletor);
        if (!setterMethod) {
            @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"老铁没有当前%@的setter",keyPath] userInfo:nil];
        }
    }
    
    - (Class)createChildClassWithKeyPath:(NSString *)keyPath{
        
        NSString *oldClassName = NSStringFromClass([self class]);
        NSString *newClassName = [NSString stringWithFormat:@"%@%@",kLGKVOPrefix,oldClassName];
        Class newClass = NSClassFromString(newClassName);
        // 防止重复创建生成新类
        if (newClass) return newClass;
        /**
         * 如果内存不存在,创建生成
         * 参数一: 父类
         * 参数二: 新类的名字
         * 参数三: 新类的开辟的额外空间
         */
        // 2.1 : 申请类
        newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
        // 2.2 : 注册类
        objc_registerClassPair(newClass);
        // 2.3.1 : 添加class : class的指向是LGPerson
        SEL classSEL = NSSelectorFromString(@"class");
        Method classMethod = class_getInstanceMethod([self class], classSEL);
        const char *classTypes = method_getTypeEncoding(classMethod);
        class_addMethod(newClass, classSEL, (IMP)lg_class, classTypes);
        // 2.3.2 : 添加setter
        SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
        Method setterMethod = class_getInstanceMethod([self class], setterSEL);
        const char *setterTypes = method_getTypeEncoding(setterMethod);
        class_addMethod(newClass, setterSEL, (IMP)lg_setter, setterTypes);
        return newClass;
    }
    

    通过LGKVOInfo保存信息

    - (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LGKeyValueObservingOptions)options{
        self = [super init];
        if (self) {
            self.observer = observer;
            self.keyPath  = keyPath;
            self.options  = options;
        }
        return self;
    }
    

    解析:
    首先,要确保被观察者有setter方法,否则添加观察无效,被观察的对象没有通过setter方法赋新值,也就观察不到变化。
    其次,我们仿照这系统KVO动态生成一个新的子类。
    然后,我们让原来的类的isa指向这个新生成的子类。
    最后,保存观察者信息。

    • 实现子类方法
      我们自定义KVO动态添加方法的时候,isa指向了新添加的类lg_class还有lg_setter

    lg_class

    Class lg_class(id self,SEL _cmd){
        return class_getSuperclass(object_getClass(self));
    }
    

    获取的class还是原始的类的对象。

    lg_setter

    static void lg_setter(id self,SEL _cmd,id newValue){
        NSLog(@"来了:%@",newValue);
        // 4: 消息转发 : 转发给父类
        // 改变父类的值 --- 可以强制类型转换
        NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
        id oldValue       = [self valueForKey:keyPath];
        
        void (*lg_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
        // void /* struct objc_super *super, SEL op, ... */
        struct objc_super superStruct = {
            .receiver = self,
            .super_class = class_getSuperclass(object_getClass(self)),
        };
        //objc_msgSendSuper(&superStruct,_cmd,newValue)
        lg_msgSendSuper(&superStruct,_cmd,newValue);
        // 1: 拿到观察者
        NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
        
        for (LGKVOInfo *info in observerArr) {
            if ([info.keyPath isEqualToString:keyPath]) {
                dispatch_async(dispatch_get_global_queue(0, 0), ^{
                    NSMutableDictionary<NSKeyValueChangeKey,id> *change = [NSMutableDictionary dictionaryWithCapacity:1];
                    // 对新旧值进行处理
                    if (info.options & LGKeyValueObservingOptionNew) {
                        [change setObject:newValue forKey:NSKeyValueChangeNewKey];
                    }
                    if (info.options & LGKeyValueObservingOptionOld) {
                        [change setObject:@"" forKey:NSKeyValueChangeOldKey];
                        if (oldValue) {
                            [change setObject:oldValue forKey:NSKeyValueChangeOldKey];
                        }
                    }
                    // 2: 消息发送给观察者
                    SEL observerSEL = @selector(lg_observeValueForKeyPath:ofObject:change:context:);
                    objc_msgSend(info.observer,observerSEL,keyPath,self,change,NULL);
                });
            }
        }
        
    }
    

    解析:
    1、先处理父类,改变父类的值,把消息发给父类。(因为我们通过前面的了解知道,KVO是私底下生成了一个被观察者的类的子类,所以父类就是被观察者的类)。
    2、获取观察者,观察者之前被保存了。我们通过遍历来找到匹配的属性,进一步的去处理变化。
    3、处理新旧值。
    4、值改变后通知观察者。

    • 移除观察者lg_removeObserver
    - (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
        
        NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
        if (observerArr.count<=0) {
            return;
        }
        
        for (LGKVOInfo *info in observerArr) {
            if ([info.keyPath isEqualToString:keyPath]) {
                [observerArr removeObject:info];
                objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
                break;
            }
        }
    
        if (observerArr.count<=0) {
            // 指回给父类
            Class superClass = [self class];
            object_setClass(self, superClass);
        }
    }
    

    解析:
    1、匹配到观察的对象之前保存的信息,然后移除掉;
    2、isa指针指回原始类对象。

    至此,自定义KVO的添加,值变化,通知值变化,销毁整个过程就完毕。

    函数式KVO
    • 函数式编程介绍

    编程范式指的是编写命令的方法。编程语言的思想正是建立在其编程范式之上。最常见的三种范式分别是面向对象程序设计、命令式程序设计和函数式程序设计。这三种思想体系并无优劣之分,通常我们都需要选择正确的工具来完成工作。

    和指令式编程相比,函数式编程强调函数的计算比指令的执行重要。

    和过程化编程相比,函数式编程里函数的计算可随时调用。

    简单说,"函数式编程"是一种"编程范式"(programming paradigm),也就是如何编写程序的方法论。

    它属于"结构化编程"的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用。

    函数式编程关心数据的映射,命令式编程关心解决问题的步骤。

    • 定义响应的回调
    typedef void(^LGKVOBlock)(id observer,NSString *keyPath,id oldValue,id newValue);
    
    • 注册函数式KVO addObserver
      相当于在原来的自定义的addObserver方法里,把定义的block再传进来,进行信息的保存。
    - (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(LGKVOBlock)block {
    ……
        // 4: 保存信息
        LGInfo *info = [[LGInfo alloc] initWitObserver:observer forKeyPath:keyPath handleBlock:block];
    ……
    }
    
    • 函数式KVO setter
    static void lg_setter(id self,SEL _cmd,id newValue){
        ······
        // 5: 信息数据回调
        NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
        
        for (LGInfo *info in mArray) {
            if ([info.keyPath isEqualToString:keyPath] && info.handleBlock) {
                info.handleBlock(info.observer, keyPath, oldValue, newValue);
            }
        }
    }
    

    在前面自定义KVO中,在setter方法中进行了新值和旧值的分别处理。现在的block方式的封装,是通过这个block把所有的相关信息都通过这个block返回了,这样就实现了在添加观察者的后面,直接去处理结果,代码更加集中。

    同样,关于kvo信息保存的部分,再增加一个block的存储即可。

    运行结果.png
    • 自定义KVO的自动销毁

    我们自定义完毕KVO,但是在观察者dealloc的时候需要手动remove掉观察,我们来尝试做一下自动释放,不需要主动去调用释放。

    在removeObserver的过程中主要做了两件事情,移除关联对象数组中的数据,以及isa指回。关联对象不进行移除的话是会继续被调用的,那么我们在调用的时候去判断下observer是否存在来处理下是否回调。

    • dealloc方法重写处理

    我们在想要统一处理dealloc方法时,就是VC有可能自己实现了dealloc或者,也有可能自己没有实现这两种情况。我们通过方法交换,把系统方法dealloc替换成自己的mydealloc,如果VC自己实现了dealloc直接交换就可以了,如果没有实现,那么我们为他添加一个dealloc方法,然后再进行交换。

    具体实现:
    (自己添加了一些注释,可能理解的不对,如有误,望指正)

    + (BOOL)kc_hookOrigInstanceMenthod:(SEL)oriSEL newInstanceMenthod:(SEL)swizzledSEL {
        Class cls = self;
        //原始方法
        Method oriMethod = class_getInstanceMethod(cls, oriSEL);
        
        //交换的方法
        Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
        
        //要交换的方法为空
        if (!swiMethod) {
            return NO;
        }
        
        //原始的dealloc为空 添加一个
        if (!oriMethod) {
            //添加的类名 下的orisel实现为swiMethod 直接让orisel 指向了swiMethod
            class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
            method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){ }));
        }
        
        BOOL didAddMethod = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
        //方法已经被添加了
        if (didAddMethod) {
            /*cls 想要修改的类
             *swizzledSEL 你想要被替换的实现
             *method_getImplementation(oriMethod) 由cls标识的类的名称标识的方法的新实现。
             *method_getTypeEncoding(oriMethod) 描述方法参数类型的字符数组。由于函数必须至少包含两个参数self和_cmd,因此第二个和第三个字符必须为“@:”(第一个字符是返回类型)。
             */
            
            //所以这个根据前面添加的过程 现在是用method_getImplementation(oriMethod)替换了swizzledSEL,其实swizzledSEL和method_getImplementation(oriMethod)是一样的,因为前面添加oriMethod的实现的时候 是把swiMethod的实现赋值了过去。
            class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
        }else{
            //如果添加失败 那么说明原来就有这个方法 直接交换imp
            method_exchangeImplementations(oriMethod, swiMethod);
        }
        return YES;
    }
    
    class_replaceMethod.png
    • class_replaceMethod和method_exchangeImplementations的区别
      class_replaceMethod只修改前面被替换的那个IMP,后面的IMP不变;
      method_exchangeImplementations相互交换IMP,两个方法的指向都变了。

    如果我们在分类里通过load方法这么搞,会把所有的dealloc方法都替换掉,这样不合理,我们目的是处理KVO的自动销毁,而不影响到系统方法的正常使用,所以这个交换放到动态创建子类的时候比较合适,创建过子类实现dealloc方法交换。

    - (Class)createChildClassWithKeyPath:(NSString *)keyPath{
        
        NSString *oldClassName = NSStringFromClass([self class]);
        NSString *newClassName = [NSString stringWithFormat:@"%@%@",kLGKVOPrefix,oldClassName];
        Class newClass = NSClassFromString(newClassName);
        // 防止重复创建生成新类
        if (newClass) return newClass;
        // 2.1 : 申请类
        newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
        // 2.2 : 注册类
        objc_registerClassPair(newClass);
        
        // 2.3.1 : 添加class : class的指向是LGPerson
        SEL classSEL = NSSelectorFromString(@"class");
        Method classMethod = class_getInstanceMethod([self class], classSEL);
        const char *classTypes = method_getTypeEncoding(classMethod);
        class_addMethod(newClass, classSEL, (IMP)lg_class, classTypes);
        
        // 2.3.2 : 添加setter
        SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
        Method setterMethod = class_getInstanceMethod([self class], setterSEL);
        const char *setterTypes = method_getTypeEncoding(setterMethod);
        class_addMethod(newClass, setterSEL, (IMP)lg_setter, setterTypes);
        
    //替换掉dealloc方法
           [self kc_hookOrigInstanceMenthod:NSSelectorFromString(@"dealloc") newInstanceMenthod:@selector(myDealloc)];
        
        return newClass;
    }
    
    - (void)myDealloc{
        NSMutableArray *observerdArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
            for (LGInfo *info in observerdArray) {
                if (info.observer) {
                    [info.observer lg_removeObserver:self forKeyPath:info.keyPath];
                }
            }
    }
    
    FBKVOController

    上述自定义的KVO代码并不完善,通过实践来感受一些KVO使用的痛点,下面我们通过FBKVOController,由Facebook自定义的KVO,项目已经开源。

    特点:
    1、采用代理模式,对常用的KVO进行封装;
    2、可以对model中的多个属性进行监听;
    3、提供action和block两种方式回调,业务代码更加紧凑;
    4、提供自动销毁机制,不用自行移除观察者。

    • 部分源码解析

    初始化:

    - (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)retainObserved
    {
      self = [super init];
      if (nil != self) {
          //一般情况下 observer 会持有 FBKVOController 为了避免循环引用,此处的_observer是弱引用
          _observer = observer;
          
          //定义 NSMapTable key的内存管理策略,在默认情况,传入的参数 retainObserved = YES
          NSPointerFunctionsOptions keyOptions = retainObserved ? NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPointerPersonality : NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality;
     
          //创建 NSMapTable
          _objectInfosMap = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality capacity:0];
          
          //初始化互斥锁加锁
          pthread_mutex_init(&_lock, NULL);
      }
      return self;
    }
    

    注册观察者:

    - (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block
    {
      NSAssert(0 != keyPath.length && NULL != block, @"missing required parameters observe:%@ keyPath:%@ block:%p", object, keyPath, block);
      if (nil == object || 0 == keyPath.length || NULL == block) {
        return;
      }
    
      // create info 存储观察者信息
      _FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options block:block];
    
      // observe object with info observe和info的关联
      [self _observe:object info:info];
    }
    

    observe和info的关联:

    去判断info,没有的话去创建,有的话直接返回。

    - (void)_observe:(id)object info:(_FBKVOInfo *)info
    {
      // lock
      pthread_mutex_lock(&_lock);
    
        //取出观察者相关信息
      NSMutableSet *infos = [_objectInfosMap objectForKey:object];
    
      // check for info existence 判断是否为空
      _FBKVOInfo *existingInfo = [infos member:info];
      if (nil != existingInfo) {
        // observation info already exists; do not observe it again
    
        // unlock and return
        pthread_mutex_unlock(&_lock);
        return;
      }
    
      // lazilly create set of infos 如果为空 创建info给对应的object
      if (nil == infos) {
        infos = [NSMutableSet set];
        [_objectInfosMap setObject:infos forKey:object];
      }
    
      // add info and oberve
      [infos addObject:info];
    
      // unlock prior to callout
      pthread_mutex_unlock(&_lock);
    
        //info的一些处理判断 更新观察状态
      [[_FBKVOSharedController sharedController] observe:object info:info];
    }
    

    FBKVOController回调:

    - (void)observeValueForKeyPath:(nullable NSString *)keyPath
                          ofObject:(nullable id)object
                            change:(nullable NSDictionary<NSString *, id> *)change
                           context:(nullable void *)context
    {
      NSAssert(context, @"missing context keyPath:%@ object:%@ change:%@", keyPath, object, change);
    
      _FBKVOInfo *info;
    
      {
        // lookup context in registered infos, taking out a strong reference only if it exists
        pthread_mutex_lock(&_mutex);
        info = [_infos member:(__bridge id)context];
        pthread_mutex_unlock(&_mutex);
      }
    
      if (nil != info) {
    
        // take strong reference to controller
        FBKVOController *controller = info->_controller;
        if (nil != controller) {
    
          // take strong reference to observer
          id observer = controller.observer;
          if (nil != observer) {
    
            // dispatch custom block or action, fall back to default action
            if (info->_block) {
              NSDictionary<NSString *, id> *changeWithKeyPath = change;
              // add the keyPath to the change dictionary for clarity when mulitple keyPaths are being observed
              if (keyPath) {
                NSMutableDictionary<NSString *, id> *mChange = [NSMutableDictionary dictionaryWithObject:keyPath forKey:FBKVONotificationKeyPathKey];
                [mChange addEntriesFromDictionary:change];
                changeWithKeyPath = [mChange copy];
              }
              info->_block(observer, object, changeWithKeyPath);
            } else if (info->_action) {
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
              [observer performSelector:info->_action withObject:change withObject:object];
    #pragma clang diagnostic pop
            } else {
              [observer observeValueForKeyPath:keyPath ofObject:object change:change context:info->_context];
            }
          }
        }
      }
    }
    

    通过info找到相关的controller,然后经过非空判断,进行change的处理和回调。

    FBKVOController 的销毁:

    - (void)dealloc
    {
      [self unobserveAll];
      pthread_mutex_destroy(&_lock);
    }
    
    - (void)unobserveAll
    {
      [self _unobserveAll];
    }
    
    - (void)_unobserveAll
    {
      // lock
      pthread_mutex_lock(&_lock);
    
      NSMapTable *objectInfoMaps = [_objectInfosMap copy];
    
      // clear table and map
      [_objectInfosMap removeAllObjects];
    
      // unlock
      pthread_mutex_unlock(&_lock);
    
      _FBKVOSharedController *shareController = [_FBKVOSharedController sharedController];
    
      for (id object in objectInfoMaps) {
        // unobserve each registered object and infos
        NSSet *infos = [objectInfoMaps objectForKey:object];
        [shareController unobserve:object infos:infos];
      }
    }
    
    - (void)unobserve:(id)object infos:(nullable NSSet<_FBKVOInfo *> *)infos
    {
      if (0 == infos.count) {
        return;
      }
    
      // unregister info
      pthread_mutex_lock(&_mutex);
      for (_FBKVOInfo *info in infos) {
        [_infos removeObject:info];
      }
      pthread_mutex_unlock(&_mutex);
    
      // remove observer
      for (_FBKVOInfo *info in infos) {
        if (info->_state == _FBKVOInfoStateObserving) {
          [object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
        }
        info->_state = _FBKVOInfoStateNotObserving;
      }
    }
    

    销毁的部分主要是加锁,然后去遍历_objectInfosMap表中的信息,拿到infos,通过方法unobserve去处理infos,再去遍历infos把里面的info信息都清空。

    GNUStep Base

    KVOKVC的代码苹果没有开源,我们可以通过GNUstep去探索下他们的原理。

    • 源码看KVO注册

    阅读的时候添加了一些相关注释

    - (void) addObserver: (NSObject*)anObserver
          forKeyPath: (NSString*)aPath
             options: (NSKeyValueObservingOptions)options
             context: (void*)aContext
    {
      GSKVOInfo             *info; //和我们自定义的KVO时候的思路相似,用一个info对象来存储注册过的需要观察的对象
      GSKVOReplacement      *r;
        //r里面包括原类和被替换之后的类的信息,下面是GSKVOReplacement包括的内容
        ///Class original;                    /* The original class */
        ///Class replacement;            /* The replacement class */
        ///NSMutableSet  *keys;        /* The observed setter keys */
    
      NSKeyValueObservationForwarder *forwarder; //这个里面大概装了一些观察的属性的状态 新值旧值这些
      NSRange               dot;
    
      setup(); //一些表的初始化
    
      [kvoLock lock];
    
      // Use the original class 给原类创建一个子类
      r = replacementForClass([self class]);
    
      /*
       * Get the existing observation information, creating it (and changing
       * the receiver to start key-value-observing by switching its class)
       * if necessary.
       */
    
      //info为空 初始化info
      info = (GSKVOInfo*)[self observationInfo];
      if (info == nil)
        {
          info = [[GSKVOInfo alloc] initWithInstance: self];
          [self setObservationInfo: info];
          object_setClass(self, [r replacement]);
        }
    
      /*
       * Now add the observer.
       */
      dot = [aPath rangeOfString:@"."];
        //判断是不是keypath的方式进行的注册
      if (dot.location != NSNotFound)
        {
          forwarder = [[NSKeyValueObservationForwarder alloc]
            initWithKeyPath: aPath
               ofObject: self
             withTarget: anObserver
            context: aContext];
          [info addObserver: anObserver
                 forKeyPath: aPath
                    options: options
                    context: forwarder];
        }
      else
        {
            //不是keypath的方式
          [r overrideSetterFor: aPath];
          [info addObserver: anObserver
                 forKeyPath: aPath
                    options: options
                    context: aContext];
        }
    
      [kvoLock unlock];
    }
    
    • replacementForClass
    static GSKVOReplacement *
    replacementForClass(Class c)
    {
      GSKVOReplacement *r;
    
      setup();
      [kvoLock lock];
      r = (GSKVOReplacement*)NSMapGet(classTable, (void*)c);
      if (r == nil)
        {
          r = [[GSKVOReplacement alloc] initWithClass: c];
          NSMapInsert(classTable, (void*)c, (void*)r);
        }
      [kvoLock unlock];
      return r;
    }
    
    - (id) initWithClass: (Class)aClass
    {
      NSValue       *template;
      NSString      *superName;
      NSString      *name;
    
      if (nil == (self = [super init]))
        {
          return nil;
        }
    
      if ([aClass instanceMethodForSelector: @selector(takeValue:forKey:)]
        != [NSObject instanceMethodForSelector: @selector(takeValue:forKey:)])
        {
          NSLog(@"WARNING The class '%@' (or one of its superclasses) overrides"
            @" the deprecated takeValue:forKey: method.  Using KVO to observe"
            @" this class may interfere with this method.  Please change the"
            @" class to override -setValue:forKey: instead.",
            NSStringFromClass(aClass));
        }
      if ([aClass instanceMethodForSelector: @selector(takeValue:forKeyPath:)]
        != [NSObject instanceMethodForSelector: @selector(takeValue:forKeyPath:)])
        {
          NSLog(@"WARNING The class '%@' (or one of its superclasses) overrides"
            @" the deprecated takeValue:forKeyPath: method.  Using KVO to observe"
            @" this class may interfere with this method.  Please change the"
            @" class to override -setValue:forKeyPath: instead.",
            NSStringFromClass(aClass));
        }
      original = aClass;
    
      /*
       * Create subclass of the original, and override some methods
       * with implementations from our abstract base class.
       */
      superName = NSStringFromClass(original);
      name = [@"GSKVO" stringByAppendingString: superName];
      template = GSObjCMakeClass(name, superName, nil);
      GSObjCAddClasses([NSArray arrayWithObject: template]);
      replacement = NSClassFromString(name);
      GSObjCAddClassBehavior(replacement, baseClass);
    
      /* Create the set of setter methods overridden.
       */
      keys = [NSMutableSet new];
    
      return self;
    }
    
    • 通过部分注释,我们可以看到initWithClass方法为 原类创建了一个subclass,并且这个子类重载了一些基类的方法实现。

    • GSObjCMakeClass内部通过方法objc_allocateClassPair创建了新类,新类的父类是这个原类。

    • GSObjCAddClasses通过方法objc_registerClassPair注册类。

    • GSObjCAddClassBehavior处理method

    • 源码看KVO的setter处理

    image.png

    标记了红色的部分是对set进行简单的处理,for里面有个判断是关于settet方法是否找到的,如果查到了相关的setter方法,keys会add这个key,如果没查到会走KVC的流程继续查,还未找到setter的方法报错。

    image.png
    • setter
    - (void) setter: (void*)val
    {
      NSString  *key;
      Class     c = [self class];
      void      (*imp)(id,SEL,void*);
    
      imp = (void (*)(id,SEL,void*))[c instanceMethodForSelector: _cmd];
    
      key = newKey(_cmd);
        //自动开关是否开启的判断,开启了走willChangeValueForKey ,setter赋值,didChangeValueForKey
      if ([c automaticallyNotifiesObserversForKey: key] == YES)
        {
          // pre setting code here
          [self willChangeValueForKey: key];
          (*imp)(self, _cmd, val);
          // post setting code here
          [self didChangeValueForKey: key];
        }
      else
        {
            //未开启直接赋值不走willChangeValueForKey和didChangeValueForKey
          (*imp)(self, _cmd, val);
        }
      RELEASE(key);
    }
    
    
    • willChangeValueForKey
    - (void) willChangeValueForKey: (NSString*)aKey
    {
      GSKVOPathInfo *pathInfo;
      GSKVOInfo     *info;
    
      info = (GSKVOInfo *)[self observationInfo];
      if (info == nil)
        {
          return;
        }
    
      pathInfo = [info lockReturningPathInfoForKey: aKey];
      if (pathInfo != nil)
        {
          if (pathInfo->recursion++ == 0)
            {
                //原来的新值 变成旧值
              id    old = [pathInfo->change objectForKey: NSKeyValueChangeNewKey];
    
                // 旧值存在
              if (old != nil)
                {
                  /* We have set a value for this key already, so the value
                   * we set must now be the old value and we don't need to
                   * refetch it.
                   */
                    //NSKeyValueChangeOldKey 下的旧值更新
                  [pathInfo->change setObject: old
                                       forKey: NSKeyValueChangeOldKey];
                    //移除掉没set之前的新值
                  [pathInfo->change removeObjectForKey: NSKeyValueChangeNewKey];
                }
              else if (pathInfo->allOptions & NSKeyValueObservingOptionOld)
                {
                    //原来没有值 那么旧值也是空的
                  /* We don't have an old value set, so we must fetch the
                   * existing value because at least one observation wants it.
                   */
                  old = [self valueForKey: aKey];
                  if (old == nil)
                    {
                      old = null;
                    }
                  [pathInfo->change setObject: old
                                       forKey: NSKeyValueChangeOldKey];
                }
              [pathInfo->change setValue:
                [NSNumber numberWithInt: NSKeyValueChangeSetting]
                forKey: NSKeyValueChangeKindKey];
    
              [pathInfo notifyForKey: aKey ofInstance: [info instance] prior: YES];
            }
          [info unlock];
        }
    
      [self willChangeValueForDependentsOfKey: aKey];
    }
    

    以上添加了注释,简单的理解就是willChangeValueForKey是对旧值进行处理,如果在这之前原来有旧值 那么更新下,此刻新值成为了旧值,旧值被替换为上一次值变化的新值,新值被remove掉。
    如果之前都没有值就都是NULL。

    • didChangeValueForKey
    image.png

    简述,此方法是去更新新值,并且通过方法notifyForKey发送通知。

    • 源码看KVO removeObserver
    image.png

    对相关的info进行移除,并且isa指回原类。

    以上就是通过GNU step了解到的KVO的一部分实现逻辑,里面还有大量的代码需要去学习,这看到的只是冰山一角,了解了一些大概,里面还有很详细对新旧值的各种处理和判断,还有setter方法对不同类型的值进行的处理等等,以后若时间充足可以更加深入和详细的研究。

    相关文章

      网友评论

          本文标题:21.iOS底层学习之KVO自定义

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