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