KVO自定义

KVO自定义的思路

  • isa指向动态子类
  • 子类 很多方法
  • dealloc - class - setName - LGPerson - 消息回调
  • remove

KVO自定义动态生成子类

上篇文章,我们对KVO的底层进行了详细的分析,那么这篇文章,就仿照 KVO 的实现进行自定义。因为在底层中,KVO是以 NSObject+NSKeyValueObserving分类的形式展示出来的,那么我们自定义,也先创建 NSObject+LGKVO 的分类。

- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;

- (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
  • 验证是否存在setter方法
#pragma mark - 验证是否存在setter方法
- (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];
    }
}

下面验证judgeSetterMethodFromKeyPath方法

//LGPerson.h文件,name为成员变量 
@interface LGPerson : NSObject{
    @public
    NSString *name;
}

//添加KVO属性监听
[self.person lg_addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];

//更改name的值
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    self.person->name = [NSString stringWithFormat:@"%@ *", self.person.kcName];
}

运行工程发生崩溃,成功验证judgeSetterMethodFromKeyPath方法

image.png
  • KVO核心-isa_siwziling动态生成子类

思路:
1、验证动态子类是否已经创建
如果是已创建了,就直接返回
如果是未创建,就进入第二步
2、创建子类
先申请类,需要父类、新类的名称、开辟新空间
再注册类
最后添加方法,需要 class 、 setter方法

- (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;
}

#pragma mark - 从get方法获取set方法的名称 key ===>>> setKey:
static NSString *setterForGetter(NSString *getter){
    
    if (getter.length <= 0) { return nil;}
    
    NSString *firstString = [[getter substringToIndex:1] uppercaseString];
    NSString *leaveString = [getter substringFromIndex:1];
    
    return [NSString stringWithFormat:@"set%@%@:",firstString,leaveString];
}

下面测试添加的方法是否能够执行,这里测试的是lg_setter方法

// NSObject+LGKVO.m文件
static void lg_setter(id self,SEL _cmd,id newValue){
    NSLog(@"来了:%@",newValue);
}

// LGPerson.h文件
@interface LGPerson : NSObject 
@property (nonatomic, copy) NSString *nickName;

// 添加KVO监听
[self.person lg_addObserver:self forKeyPath:@"nickName" options:(NSKeyValueObservingOptionNew) context:NULL];

// 给属性变量设值
self.person.nickName = [NSString stringWithFormat:@"%@ +", self.person.nickName];

// 运行工程,控制台成功打印,执行到lg_setter方法
2021-09-06 23:18:16.159342+0800 003---自定义KVO[23254:14997428] 来了:(null) +

自定义KVO的setter

static void lg_setter(id self,SEL _cmd,id newValue){
    NSLog(@"来了:%@",newValue);
    // 4: 消息转发 : 转发给父类
    // 改变父类的值 --- 可以强制类型转换
    
    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);
    
    // 既然观察到了,下一步不就是回调 -- 让我们的观察者调用
    // - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
    // 1: 拿到观察者
    id observer = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
    
    // 2: 消息发送给观察者
    SEL observerSEL = @selector(observeValueForKeyPath:ofObject:change:context:);
    NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
    objc_msgSend(observer,observerSEL,keyPath,self,@{keyPath:newValue},NULL);    
}

下面验证LGPersonsetNickName方法是否执行?

// LGPerson.m文件
- (void)setNickName:(NSString *)nickName{
    NSLog(@"来到 LGPerson 的setter方法 :%@",nickName);
    _nickName = nickName;
}

//同上给person添加nickName属性的KVO监听,运行工程,控制台打印如下
2021-09-06 23:36:41.956811+0800 003---自定义KVO[23351:15010309] 来了:(null) +
2021-09-06 23:36:41.957009+0800 003---自定义KVO[23351:15010309] 来到 LGPerson 的setter方法 :(null) +
2021-09-06 23:36:41.957269+0800 003---自定义KVO[23351:15010309] {
    nickName = "(null) +";
}

从打印信息中可以看出,执行了setNickName方法

自定义KVO的多元素观察

查看isa的指向

image.png

这步需要新建一个LGKVOInfo继承于NSObject,用来保存观察者信息,将多个LGKVOInfo实例添加到数组中,再通过关联对象保存到self中进行相关的操作。 详细代码如下

// LGKVOInfo.h文件
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

typedef NS_OPTIONS(NSUInteger, LGKeyValueObservingOptions) {
    LGKeyValueObservingOptionNew = 0x01,
    LGKeyValueObservingOptionOld = 0x02,
};

@interface LGKVOInfo : NSObject
@property (nonatomic, weak) NSObject  *observer;
@property (nonatomic, copy) NSString    *keyPath;
@property (nonatomic, assign) LGKeyValueObservingOptions options;

- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LGKeyValueObservingOptions)options;
@end

NS_ASSUME_NONNULL_END

// LGKVOInfo.m文件
#import "LGKVOInfo.h"
@implementation 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;
}
@end
// 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);
}
实现子类方法

在动态生成子类时,会重写class和setter方法,它们的IMP分别指向lg_class和lg_setter函数地址。通过关联对象保存到self中,这个就和上篇在分析KVO时那样,使用中间类重写后的class方法,获取的还是原始类对象,就好像KVO所做的一切都不存在似的。

  • lg_class
Class lg_class(id self,SEL _cmd){
    return class_getSuperclass(object_getClass(self));
}
  • lg_setter
    思路:
    1、消息转发:转发给父类,改变父类的值
    2、获取观察者
    3、对新、旧值进行处理
    4、发送消息给观察者
static void lg_setter(id self,SEL _cmd,id newValue){
    NSLog(@"来了:%@",newValue);

    // 1: 消息转发 : 转发给父类
    // 改变父类的值 --- 可以强制类型转换
    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);

    // 2: 拿到观察者
    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];

                //3 对新旧值进行处理
                if (info.options & LGKeyValueObservingOptionNew) {
                    [change setObject:newValue forKey:NSKeyValueChangeNewKey];
                }
                if (info.options & LGKeyValueObservingOptionOld) {
                    [change setObject:@"" forKey:NSKeyValueChangeOldKey];
                    if (oldValue) {
                        [change setObject:oldValue forKey:NSKeyValueChangeOldKey];
                    }
                }

                // 4: 消息发送给观察者
                SEL observerSEL = @selector(lg_observeValueForKeyPath:ofObject:change:context:);
                objc_msgSend(info.observer,observerSEL,keyPath,self,change,NULL);
            });
        }
    }
}
移除观察者

思路:
1.、移除观察者;
2、isa指回原始类对象

// 移除观察者方法
- (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);
    }
}
那么到了这里,KVO 的自定义就完成了,那么接下来就使用下
- (void)viewDidLoad {
    [super viewDidLoad];
    self.person = [[LGPerson alloc] init];    
    NSLog(@"注册观察者之前:%s",object_getClassName(self.person));    
    [self.person lg_addObserver:self forKeyPath:@"nickName" options:(LGKeyValueObservingOptionNew|LGKeyValueObservingOptionOld) context:NULL];
    NSLog(@"注册观察者之后:%s",object_getClassName(self.person));
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    self.person.nickName = @"kc";
}

#pragma mark - KVO回调
- (void)lg_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"%@",change);
}

- (void)dealloc{
    NSLog(@"移除观察者之前:%s",object_getClassName(self.person));
    [self.person lg_removeObserver:self forKeyPath:@"nickName"];
    NSLog(@"end");
    NSLog(@"移除观察者之后:%s",object_getClassName(self.person));
}

// 控制台打印信息
2021-09-07 21:23:24.849907+0800 003---自定义KVO[29817:15438088] 注册观察者之前:LGPerson
2021-09-07 21:23:24.850664+0800 003---自定义KVO[29817:15438088] 注册观察者之后:LGKVONotifying_LGPerson
2021-09-07 21:23:26.342697+0800 003---自定义KVO[29817:15438088] 来了:kc
2021-09-07 21:23:26.342972+0800 003---自定义KVO[29817:15438088] 来到 LGPerson 的setter方法 :kc
2021-09-07 21:23:26.343194+0800 003---自定义KVO[29817:15438180] {
    new = kc;
    old = "";
}
2021-09-07 21:23:28.312150+0800 003---自定义KVO[29817:15438088] 移除观察者之前:LGKVONotifying_LGPerson
2021-09-07 21:23:28.312708+0800 003---自定义KVO[29817:15438088] 移除观察者之后:LGPerson

自定义KVO的函数式

函数式编程是把运算过程写成一系列嵌套的函数,然后进行调用。函数式编程支持函数作为第一类对象,也可以被称为闭包或者仿函数(functor)对象
上文通过lg_observeValueForKeyPath:ofObject:change:context:这种调用方式很不方便。所以我们引入函数式思想,通过block来实现监听的回调。接下来我们就进行优化。

  • LGKVOInfo.h中,添加LGKVOBlock
@interface LGLKVOInfo : NSObject 

@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, copy) LGKVOBlock handleBlock; 
@end 

@implementation LGLKVOInfo 
- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath handleBlock:(LGKVOBlock)block { 
    if (self=[super init]) { 
        _observer = observer;
        _keyPath = keyPath; 
        _handleBlock = block; 
    } 
    return self; 
}
@end
  • NSObject+LGKVO.h中,lg_addObserver方法也添加 block
typedef void(^LGKVOBlock)(id observer,NSString *keyPath,id oldValue,id newValue); 

@interface NSObject (LGKVO) 
- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(LGKVOBlock)block; 

- (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath; 
@end
  • NSObject+LGKVO.m中,lg_addObserver方法调整
- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(LGKVOBlock)block{
    
    // 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 handleBlock:block];
    NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
    if (!mArray) {
        mArray = [NSMutableArray arrayWithCapacity:1];
        objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), mArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    [mArray addObject:info];
}
  • NSObject+SSLKVO.m中,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);
    }
}
  • NSObject+LGKVO.m中,lg_setter方法调整无需调用KVO回调方法,改为调用info中保存的block即可
static void lg_setter(id self,SEL _cmd,id newValue){

    NSLog(@"来了:%@",newValue);
    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);

    // 信息数据回调
    NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));

    for (LGKVOInfo *info in mArray) {
        if ([info.keyPath isEqualToString:keyPath] && info.handleBlock) {
            info.handleBlock(info.observer, keyPath, oldValue, newValue);
        }
    }
}
  • 优化后再在控制器里面调用就会简洁很多,代码如下
@implementation LGViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.person = [[LGPerson alloc] init];
    [self.person lg_addObserver:self forKeyPath:@"nickName" block:^(id  _Nonnull observer, NSString * _Nonnull keyPath, id  _Nonnull oldValue, id  _Nonnull newValue) {
        NSLog(@"回调方法%@-%@",oldValue,newValue);
    }];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    self.person.nickName = @"kc";
}

- (void)dealloc{
    [self.person lg_removeObserver:self forKeyPath:@"nickName"];
}

//控制台成功打印回调方法
2021-09-07 21:47:01.604882+0800 004---自定义函数式KVO[30135:15462500] 来了:kc
2021-09-07 21:47:01.605142+0800 004---自定义函数式KVO[30135:15462500] 来到 LGPerson 的setter方法 :kc
2021-09-07 21:47:01.605320+0800 004---自定义函数式KVO[30135:15462500] 回调方法(null)-kc

自定义KVO自动销毁

在使用KVO的时候,必须手动移除观察者,有时候可能会把这个环节给忽略掉,那么我们是否可以重写子类的dealloc方法,在需要其销毁的时候自动移除观察者?

  • 动态生成子类:首先在createChildClassWithKeyPath方法里面添加 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);

    // 2.3.3 : 添加dealloc
    SEL deallocSEL = NSSelectorFromString(@"dealloc");
    Method deallocMethod = class_getInstanceMethod([self class], deallocSEL);
    const char *deallocTypes = method_getTypeEncoding(deallocMethod);
    class_addMethod(newClass, deallocSEL, (IMP)lg_dealloc, deallocTypes);
    
    return newClass;
}
  • 动态生成子类时添加的dealloc方法,IMP指向lg_dealloc函数地址
static void lg_dealloc(id self,SEL _cmd) {
    Class superClass = [self class];
    object_setClass(self, superClass); 
}
  • 自动销毁机制的使用,直接在控制器里面调用dealloc方法
- (void)dealloc{
    NSLog(@"销毁 走你");
}

运行工程,进行lldb调试

image.png
image.png

成功执行到lg_dealloc销毁方法中,并且isa指针重新指向LGPerson

FBKVO的实现思想

FBKVOControllerFacebook开源的一个基于系统KVO实现的框架。支持Objective-CSwift语言。gitHub 的下载地址

FBKVOController 的优点:

  • 不需要手动移除观察者,框架自动帮我们移除观察者
  • 函数式编程,可以一行代码实现系统KVO的三个步骤。
  • 实现KVO与事件发生处的代码上下文相同,不需要跨方法传参数;
  • 增加了blockSEL自定义操作对NSKeyValueObserving回调的处理支持,提升使用 KVO的体验;
  • 每一个keyPath会对应一个block或者SEL,不需要在block中使用 if 判断 keyPath,也就是说,不需要统一的 observeValueForKeyPath方法里写 if 判断;
  • 可以同时对一个对象的多个属性进行监听,写法简洁。
  • 线程安全

FBKVOController的使用,这里是使用的block回调代码如下

#import "ViewController.h"
#import <FBKVOController.h> 
#import "LGPerson.h" 

@interface ViewController () 

@property (nonatomic, strong) FBKVOController *kvoCtrl;
@property (nonatomic, strong) LGPerson *person; 

@end 

@implementation ViewController

- (void)viewDidLoad { 
    [super viewDidLoad]; 
    self.person = [[LGPerson alloc] init]; 
    
    [self.kvoCtrl observe:self.person keyPath:@"name" options:(NSKeyValueObservingOptionNew) block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) { 
        NSLog(@"****%@****",change); 
    }]; 
} 
    
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ 
    self.person.name = @"cooci";
} 

- (FBKVOController *)kvoCtrl{ 
    if (!_kvoCtrl) { 
        _kvoCtrl = [FBKVOController controllerWithObserver:self]; 
    } 
    return _kvoCtrl;
} 

@end

除了block方式调用,还可以使用其他方式实现

  • 使用action回调
[self.kvoCtrl observe:self.person keyPath:@"age" options:NSKeyValueObservingOptionNew action:@selector(lg_observerAge)];
  • 可变数组的监听
[self.kvoCtrl observe:self.person keyPath:@"mArray" options:(NSKeyValueObservingOptionNew) block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) { 
    NSLog(@"****%@****",change); 
}];
FBKVO的实现思想
FBKVOController源码解析
  • 初始化
- (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 key为id类型,value 为 NSMapTableSet类型
    _objectInfosMap = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality capacity:0];
    // 初始化互斥锁,避免多线程间的数据竞争
    pthread_mutex_init(&_lock, NULL);
  }
  return self;
}
  • 注册观察者
  1. _FBKVOInfo类中,存储了观察者信息,包含:FBKVOControllerkeyPathoptionsblock等;
  2. observeinfo进行关联
- (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
  [self _observe:object info:info];
}
  • observeinfo的关联详情
  1. FBKVOController初始化的时候,就持有NSMapTable,使用NSMapTable代替关联对象;
  2. 然后把object作为key,获取NSMutableSet类型的value,同时NSMutableSet中存储_FBKVOInfo
  3. 如果infos为空,进行创建,并添加到NSMapTable中;
  4. infos不为空之后,将info对象添加到NSMutableSet类型的infos中;
  5. 中间者_FBKVOSharedController是单例模式,负责真正的KVO操作
- (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
  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);

  [[_FBKVOSharedController sharedController] observe:object info:info];
}
中间者_FBKVOSharedController分析
  • 注册系统KVO
  1. 传入的object为实例对象,调用系统KVO的注册观察者,开始对属性监听;
  2. 传入addObserver方法的self为当前单例的中介者;
  3. 中间者持有NSHashTable,以弱引用的方式存储_FBKVOInfo
  4. 通过_FBKVOInfo的_state成员变量,根据枚举值_FBKVOInfoStateInitial_FBKVOInfoStateObserving_FBKVOInfoStateNotObserving决定新增或删除KVO
- (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
  [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];
  }
}
  • 实现回调
  1. 通过info获取FBKVOController
  2. FBKVOController中,获取observer,也就是真正的VC
  3. 通过info获取block并调用,传入必要的参数;
- (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];
        }
      }
    }
  }
}
  • 销毁
- (void)dealloc
{
  [self unobserveAll];
  pthread_mutex_destroy(&_lock);
}
  • 拷贝自身持有的NSMapTable到临时表,然后将其清空,再遍历临时表,从单例的中介者中,取消实例对象infos的关联
- (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];
  }
}
  • 移除系统KVO的监听
  1. 调用系统KVO的removeObserver方法,移除观察者
  2. 销毁流程:VC销毁 --> FBKVOController销毁 --> 中间者的unobserve方法 --> 移除观察者
- (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;
  }
}

GNU关于KVO源码

GUN源码下载

  • NSKeyValueObserving.m文件入口开始查看
// GNU源码入口
@implementation NSObject (NSKeyValueObserving)

- (void) observeValueForKeyPath: (NSString*)aPath
               ofObject: (id)anObject
             change: (NSDictionary*)aChange
                context: (void*)aContext
{
  [NSException raise: NSInvalidArgumentException
              format: @"-%@ cannot be sent to %@ ..."
              @" create an instance overriding this",
              NSStringFromSelector(_cmd), NSStringFromClass([self class])];
  return;
}

@end

// 具体实现
@implementation NSObject (NSKeyValueObserverRegistration)

- (void) addObserver: (NSObject*)anObserver
      forKeyPath: (NSString*)aPath
         options: (NSKeyValueObservingOptions)options
         context: (void*)aContext
{
  GSKVOInfo             *info;
  GSKVOReplacement      *r;
  NSKeyValueObservationForwarder *forwarder;
  NSRange               dot;
  // 准备阶段
  setup();
  [kvoLock lock];

  // Use the original class  isa_siwziling动态生成子类,主要是类的开辟的一些处理
  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 = (GSKVOInfo*)[self observationInfo];
  if (info == nil)
    {
      info = [[GSKVOInfo alloc] initWithInstance: self];
      // 关联info
      [self setObservationInfo: info];
      object_setClass(self, [r replacement]);
    }
    
  /*
   * Now add the observer.  多个键值的keyPath的处理
   */
  dot = [aPath rangeOfString:@"."];
  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
    {
      // 重写当前set方法
      [r overrideSetterFor: aPath];
      [info addObserver: anObserver
             forKeyPath: aPath
                options: options
                context: aContext];
    }

  [kvoLock unlock];
}
  • 移除KVO源码如下
/*
 * removes the observer
 */
- (void) removeObserver: (NSObject*)anObserver forKeyPath: (NSString*)aPath
{
  GSKVOPathInfo *pathInfo;

  [iLock lock];
  pathInfo = (GSKVOPathInfo*)NSMapGet(paths, (void*)aPath);
  if (pathInfo != nil)
    {
      unsigned  count = [pathInfo->observations count];

      pathInfo->allOptions = 0;
      while (count-- > 0)
        {
          GSKVOObservation      *o;

          o = [pathInfo->observations objectAtIndex: count];
          if (o->observer == anObserver || o->observer == nil)
            {
              [pathInfo->observations removeObjectAtIndex: count];
              if ([pathInfo->observations count] == 0)
                {
                  NSMapRemove(paths, (void*)aPath);
                }
            }
          else
            {
              pathInfo->allOptions |= o->options;
            }
    }
    }
  [iLock unlock];
}
  • GNU使用如下
image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容