KVO

kvo :
1).观察这模式的一种体现 ;
2).然后原理是,通过isa 混写技术 实现了 把 原实例对象的isa (比如指向OriginClass) 指向了新的子类(NSKVONotifing_OriginClass)
3).通过 重写 setValue:方法 通过在 内部 实现willChargeForValue:(就是监听的老值)didChargeForValue: (监听新值)

1. 普通的 kvo监听

实现 selftest 对象的监听

- (void)kvoTest {
    
    Test * test = [Test  new];
    [test addObserver:self forKeyPath:@"value" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
    
    test.value = 2;
}


- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context {
    
    NSLog(@"keyPath = %@;change = %@",keyPath,change);
}
被监听的对象的isa 指针 ,已经指向了 NSKVONotifing_Test

2. 第三方的库KVOController

- (void)fbKVO {
    
    
    Test * test = [Test  new];

    self.kvo = [[FBKVOController alloc]initWithObserver:self];

    [self.kvo observe:test keyPath:@"value" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary * _Nonnull change) {
        
        NSLog(@"observer:%@",observer);
        NSLog(@"object = %@",object);
        NSLog(@"change = %@",change);
    }];
    
    
    test.value = 2;

}

解析fbkvo

FBKVO 其实是封装了系统的KVO;
主要有 被观察者 对象 object , 观察者的信息_FBKVOInfo;

FBKVOInfo:
   属性1:FBKVOController  => 封装了一个 观察者(弱引用)
   属性2:_keyPath (观察者的keyPath)
   属性3:NSKeyValueObservingOptions (新旧值)
   属性4:FBKVONotificationBlock (回调值 (id observer, id object, NSDictionary *change))

添加 监听 是通过 单例 [_FBKVOSharedController sharedController] 把我们的 objectFBKVOInfo 添加进来[object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info];

- (void)observe:(id)object info:(nullable _FBKVOInfo *)info
{
  if (nil == info) {
    return;
  }

  // register info
  pthread_mutex_lock(&_mutex);
  [_infos addObject:info];
  pthread_mutex_unlock(&_mutex);

  // add observer, 单例 作为系统的观察者, 把info 传入context,后面监听值的时候 可以方便的取值
  [object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info];

  if (info->_state == _FBKVOInfoStateInitial) {
    info->_state = _FBKVOInfoStateObserving;
  } else if (info->_state == _FBKVOInfoStateNotObserving) {
    // this could happen when `NSKeyValueObservingOptionInitial` is one of the NSKeyValueObservingOptions,
    // and the observer is unregistered within the callback block.
    // at this time the object has been registered as an observer (in Foundation KVO),
    // so we can safely unobserve it.
    [object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
  }
}

单例[_FBKVOSharedController sharedController] 获取监听

- (void)observeValueForKeyPath:(nullable NSString *)keyPath
                      ofObject:(nullable id)object
                        change:(nullable NSDictionary *)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 *changeWithKeyPath = change;
          // add the keyPath to the change dictionary for clarity when mulitple keyPaths are being observed
          if (keyPath) {
            NSMutableDictionary *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];
        }
      }
    }
  }
}

3. 自定义KVO

主要是 在监听某个object的时候,去让 object的 isa 指针 指向 子类,并且在子类中 添加 当前的 keyPathsetter 方法。
这样外界 调用了这个setter方法,就会先进入 子类setter 方法,这样我们就可以 自定义操作了。

#import "NSObject+KVOBlock.h"
#import 

static char * __KVOListKey = "__KVOListKey";


@implementation NSObject (KVOBlock)


- (void)observerkeyPath:(NSString *)keyPath
                  block:(KVOBlock)block {
    
    [self registerSubClassWithKeyPath:keyPath];
    if (block && keyPath) {
        self.map[keyPath] = [block copy];
    }
}


// 管理 keyPath 和 block 回调
- (NSMutableDictionary *)map {
    NSMutableDictionary * dic = objc_getAssociatedObject(self, &__KVOListKey);
    if (dic == nil) {
        dic = [NSMutableDictionary dictionary];
        objc_setAssociatedObject(self, &__KVOListKey, dic, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    return dic;
}


// 注册类对,就是子类
- (void)registerSubClassWithKeyPath:(NSString *)keyPath {
    
    //找到源类中的 setterMethod
    SEL setterSelector  = NSSelectorFromString([self setterFromGetter:keyPath]);
    Method setterMethod = class_getInstanceMethod([self class], setterSelector);
    if (setterMethod == nil) {
        return;
    }
    
    // 注册 self的 isa 指针为 【self class】的子类 subClass
    NSString * subClassName = [NSString stringWithFormat:@"__KVONotifying__%@", NSStringFromClass([self class])];
    Class subClass = objc_getClass(subClassName.UTF8String);
    if (subClass == nil) {
        // 传入父类 ,子类的名称
        subClass = objc_allocateClassPair([self class], subClassName.UTF8String, 0);
    }
    objc_registerClassPair(subClass);
    
    //isa 指向 subClass
    object_setClass(self, subClass);
    
    //给 子类 subClass 添加setter方法,这样 只要外界调用setter方法 就好调用 子类的setter方法
    const char * types = method_getTypeEncoding(setterMethod);
    class_addMethod(subClass, setterSelector, (IMP)kvo_setter, types);
}

// 获取 setter 方法
- (NSString *)setterFromGetter:(NSString *)keyPath {
    if (keyPath.length == 0) {
        return nil;
    }
    NSString *setter = [[[keyPath substringToIndex:1] uppercaseString] stringByAppendingString:[keyPath substringFromIndex:1]];
    setter = [NSString stringWithFormat:@"set%@:", setter];
    return setter;
}

// 获取getter方法
- (NSString *)getterFromSetter:(NSString *)setter {
    
    if (setter.length == 0 && [setter hasPrefix:@"set"]) {
        return nil;
    }
    NSString * getter = [setter substringFromIndex:3];
    getter = [NSString stringWithFormat:@"%@%@",[getter substringToIndex:1].lowercaseString,[getter substringFromIndex:1]];
    if ([getter hasSuffix:@":"]) {
        getter = [getter substringToIndex:getter.length-1];
    }
    return getter;
}


// 监听setter 方法
static void kvo_setter(id self, SEL _cmd, id newValue) {
    
    
    NSString *setterName = NSStringFromSelector(_cmd);
    NSString *getterName = [self getterFromSetter:setterName];
    if (getterName == nil) {
        return;
    }
    id oldValue = [self valueForKey:getterName];

    // https://tech.glowing.com/cn/implement-kvo/
    struct objc_super superclazz = {
        .receiver = self,
        .super_class = class_getSuperclass(object_getClass(self))
    };
    void (* objc_msgSendSuperCasted)(void *, SEL, id) = (void *)objc_msgSendSuper;
    // 当的【self class】 其实就是subClass, 所以它的superClass,就是源类
    objc_msgSendSuperCasted(&superclazz, _cmd, newValue);
    
    NSMutableDictionary *change = [NSMutableDictionary dictionary];
    change[@"keyPath"] = getterName;
    if (newValue) {
        change[NSKeyValueChangeNewKey] = newValue;
    }
    if (oldValue) {
        change[NSKeyValueChangeOldKey] = oldValue;
    }
    
    NSDictionary * dic = [self map];
    KVOBlock  block = dic[getterName];
    if (block) {
        block(change);
    }
}




@end

例子

    Test * test = [Test  new];
//    self.test = test;
    [test observerkeyPath:@"num" block:^(NSDictionary *change) {
        
        NSLog(@"num = %@",change);
    }];
    
    test.num = @(10);

你可能感兴趣的:(KVO)