美文网首页
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