自己实现OC的KVO

2017-12-19  本文已影响0人  Hardy_Hu

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。有不对的地方欢迎指正,有任何疑问欢迎留言。

上一篇下一篇

猜你喜欢

热点阅读