美文网首页
自己实现OC的KVO

自己实现OC的KVO

作者: Hardy_Hu | 来源:发表于2017-12-19 16:38 被阅读0次

    Runtime系列文章在这:
    Runtime介绍---术语介绍
    Runtime应用--动态添加方法
    Runtime应用--给分类添加属性
    平时我们使用苹果原生的KVO非常方便,添加一个观察者以及一个回调就能方便监听属性值的变化。

    // 使用时直接add观察者
    - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context
    
    // 当被观察的属性值发送改变时,会回调下面的方法
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
    

    今天我们利用Runtime自己来实现一个KVO,达到监听属性值变化的目的。

    首先我们创建NSObject的一个分类,并提供一个添加观察者的方法

    #import <Foundation/Foundation.h>
    
    @interface NSObject (HT_KVO)
    
    - (void)ht_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
    
    @end
    

    在.m文件中实现addObsever这个方法

    @implementation NSObject (HT_KVO)
    
    - (void)ht_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context
    {
        // 1. 动态创建一个子类
        // 拿到类名
       NSString *oldClassName = NSStringFromClass([self class]);
        //
        NSString *newClassName = [@"htKVO_" stringByAppendingString:oldClassName];
        
        const char *newName = [newClassName UTF8String];
        // 创建类
       Class kvoClass =  objc_allocateClassPair([self class], newName, 0);
    
        // 注册类
        objc_registerClassPair(kvoClass);
        
        //添加set方法
        // cls 添加新方法的类;  name 表示selector的方法名称;  imp 指向一个方法的实现,可以根据喜好进行命名; types 表示我们要添加方法的返回值和参数:  v 代表函数的返回值类型 void,  :@ 表示调用setName:函数的时的参数
        class_addMethod(kvoClass, @selector(setName:), (IMP)setName, "v@:@");
        
        // 修改isa指针,
        object_setClass(self, kvoClass);
        
        // 保存观察者对象
        objc_setAssociatedObject(self, @"kvo_key", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    @end
    

    并将(IMP)setName函数指针实现

    void setName(id self, SEL _cmd, NSString *newName)
    {
       
        // 保存该类的类型
        id class = [self class];
        // 修改isa指针,
        object_setClass(self, class_getSuperclass(class));
        // 调用父类的方法
        objc_msgSend(self, @selector(setName:),newName);
        // 拿到观察者
        id objc = objc_getAssociatedObject(self, @"kvo_key");
        // 通知观察者方法
        objc_msgSend(objc, @selector(observeValueForKeyPath:ofObject:change:context:),@"Name",objc,@{@"new":newName},nil);
        object_setClass(self, class);
    }
    

    我们现在来看看调用这个KVO的实际效果

    #import "ViewController.h"
    #import "NSObject+HT_KVO.h"
    #import "Son.h"
    
    @interface ViewController ()
    
    @property (nonatomic,strong) Son *son;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.son = [[Son alloc] init];
        [self.son ht_addObserver:self forKeyPath:@"Name" options:NSKeyValueObservingOptionNew context:nil];
        NSLog(@"%@",[self.son class]);
    }
    
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
    {
        NSLog(@"监听到了KVO--%@",keyPath);
        NSLog(@"新值是多少:%@",change[@"new"]);
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        static int i = 0;
        i++;
         NSLog(@"%@",[self.son class]);
        self.son.Name = [NSString stringWithFormat:@"%d",i];
         NSLog(@"%@",[self.son class]);
    }
    @end
    

    在控制台打印下输出,可以看到能正常回调。


    image.png

    将Demo上传GitHub。有不对的地方欢迎指正,有任何疑问欢迎留言。

    相关文章

      网友评论

          本文标题:自己实现OC的KVO

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