KVO原理以及自定义KVO

   系统的KVO的用法和现象

     有一个KVOModel类,只要一个名字为name的属性,现在要监听name属性的变化,先调用系统的KVO方法来运行,如下

    KVOModel *model = [KVOModel new];
    model.name = @"1";
    NSLog(@"---%@---",object_getClass(model));
    /**
     model:要监听的类
     self:监听的回调方法在哪个类里面写
     name:要监听的属性的名字
     options:要监听的项目,这个地方就是新旧值
     context:不是很明白,感觉可以用来区分一些东西
     **/
    [model addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
    NSLog(@"---%@---",object_getClass(model));
    model.name = @"2";
    NSLog(@"---%@---",object_getClass(model));

     看一下代码的打印结果

2018-07-24 22:29:37.365529+0800 Test[2532:260795] ---KVOModel---
2018-07-24 22:29:37.366037+0800 Test[2532:260795] ---NSKVONotifying_KVOModel---
2018-07-24 22:29:37.366246+0800 Test[2532:260795] ---NSKVONotifying_KVOModel---

      可以看到,调用了系统的addObserver方法之后,model的类型就变了,变成了NSKVONotifying_KVOModel类型了。这个地方具体的内部实现方式,通过其他的KVO原理相关的文章都可以查询得到,在此做一个简单的说明:KVO是系统使用了runtime的机制,动态的生成了一个NSKVONotifying_KVOModel类,继承自KVOModel,重写了要监听属性的set方法,这个就是为什么通过成员变量改变属性的值是触发不了KVO的,只有通过存取方法才能触发KVO的原因。并且将原类的isa指针指向了新类,也即是为什么在调用了系统的Observer方法之后,mode的类型改变了。

自定义KVO的实现

   弄清楚了上面的一些原理和步骤之后,现在自定义实现一个KVO,如下,自定义的KVODefine_addObserver来替代系统的addObserver的功能,冬天生成的类名的话没有系统那样NSKVONotifying_类这样的名字,使用了自己的KVODefine_类名字来命名

    1:有一个NSObject的分类NSObject (KVODefine),如下,具体的注释在代码里面都有

//NSObject+KVODefine.h  头文件

#import <Foundation/Foundation.h>
@interface NSObject (KVODefine)
- (void)KVODefine_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
@end
#import "NSObject+KVODefine.h"
#import <objc/runtime.h>
#import <objc/message.h>

@implementation NSObject (KVODefine)

/**
    主要思路
    1:使用runtime动态生成一个类B,继承自原类A
    2:给B类添加了一个set方法
    3:将原类A的isa指针指向B类
 **/
- (void)KVODefine_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{
    //得到原类--> 等价于object_getClass(self);
    Class oldClass = [self class];
    //得到新类的名字
    NSString *newClassName = [@"KVODefine_" stringByAppendingString:NSStringFromClass(oldClass)];
    Class newClass = objc_getClass(newClassName.UTF8String);
    if (!newClass){
        //创建新类继承自oldClass,名字为 KVODefine_原类
        newClass = objc_allocateClassPair(oldClass, newClassName.UTF8String, 0);
        //注册新类
        objc_registerClassPair(newClass);
    }
    //得到setter方法的名称
    NSString *setterName = [NSString stringWithFormat:@"set%@%@:",[[keyPath substringToIndex:1]uppercaseString],[keyPath substringFromIndex:1]];
    //获得原先setter方法的参数类型和返回值类型;
    Method method = class_getInstanceMethod(oldClass, NSSelectorFromString(setterName));
    const char *types = method_getTypeEncoding(method);
    //动态添加一个方法,方法的实现就是下面的setKVOMethod
    class_addMethod(newClass, NSSelectorFromString(setterName), (IMP)setKVOMethod, types);
    //将self的isa指针指向新类,也就是为了在调用setter方法的时候,会去新类中查找
    object_setClass(self, newClass);
    
    /**下面几个方法,只是为了取数据方便而已**/
    //在下面的setKVOMethod获取原类的旧值要使用
    objc_setAssociatedObject(self, "keyPath", keyPath, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    //设置新值的时候要使用
    objc_setAssociatedObject(self, "setterName", setterName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    //观察者,是在那个类里面实现的回调方法吧
    objc_setAssociatedObject(self, "observer", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    //传进来的context,最后要传回去
    objc_setAssociatedObject(self, "context", (__bridge id _Nullable)(context), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

void setKVOMethod(id self,SEL _cmd,id newValue){  //此时这个self的isa指针是指向新类的
    NSString *keyPath = objc_getAssociatedObject(self, "keyPath");
    NSString *setterName = objc_getAssociatedObject(self, "setterName");
    id observer = objc_getAssociatedObject(self, "observer");
    id context = objc_getAssociatedObject(self, "context");
    //存一下新类,在最后的self的isa指针还是要指向这个新类
    Class newClass = [self class];
    //将self的isa指针指向原类,为了获得旧值
    object_setClass(self, class_getSuperclass(newClass));
    //获取到旧值
    NSString *oldValue = objc_msgSend(self, NSSelectorFromString(keyPath));
    //将新值赋值一下
    objc_msgSend(self, NSSelectorFromString(setterName), newValue);
    NSMutableDictionary *change = @{}.mutableCopy;
    //设置旧值
    if (oldValue){
        change[NSKeyValueChangeOldKey] = oldValue;
    }
    //设置新值
    if (newValue){
        change[NSKeyValueChangeNewKey] = newValue;
    }
    //调用observer里面的回调方法
    objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:),keyPath,observer,change,context);
    //self的isa指针指向新类
    object_setClass(self, newClass);
}

最后这句话,将self的isa指针指向了新类,要是就但从实现KVO的功能来说,是不需要的,但是从上面可以知道,系统最终的那个对象的isa指针是指向新类的,所以这个地方为了和系统保持统一,就将isa指针的指向修改了一下。

测试代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    KVOModel *model = [KVOModel new];
    model.name = @"1";
    NSLog(@"---%@---",object_getClass(model));
    /**
     model:要监听的类
     self:监听的回调方法在哪个类里面写
     name:要监听的属性的名字
     options:要监听的项目,这个地方就是新旧值
     context:不是很明白,感觉可以用来区分一些东西
     **/
    [model KVODefine_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld context:nil];
    NSLog(@"---%@---",object_getClass(model));
    model.name = @"2";
    NSLog(@"---%@---",object_getClass(model));
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    if ([keyPath isEqualToString:@"name"]){
        NSLog(@"new:%@--old:%@",change[NSKeyValueChangeNewKey],change[NSKeyValueChangeOldKey]);
    }
}

最终的运行结果:

2018-07-24 22:48:40.797558+0800 Test[2798:304064] ---KVOModel---
2018-07-24 22:48:40.797827+0800 Test[2798:304064] ---KVODefine_KVOModel---
2018-07-24 22:48:40.798004+0800 Test[2798:304064] new:2--old:1
2018-07-24 22:48:40.798270+0800 Test[2798:304064] ---KVODefine_KVOModel---

  总的来说,1:动态生成一个继承自原类的类,2:重写set方法,3:修改isa指针的指向。最后一步不可少,因为重写的是新类的set方法,要是没有最后一步的话,就没法触发调用动态添加的那个set方法了。一点点小认识,如有不正确的地方,欢迎指正。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值