响应式编程 KVO 的原理
1.简单实现 KVO功能
- 一开始 self.person 的 isa 指针类型为 Person,当控制器被触摸时 self.person.age += 1 (调用了 Person 类的 setAge: 方法), 然后会调用监听方法,如下:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context;
- 此时通过段点可查看到 self.person 的 isa 指针为 NSKVONotifying_Person 类型,如图:
//
// Person.h
//
// Created by 瞿杰 on 2017/8/16.
// Copyright © 2017年 iThinkerYZ. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic , assign) NSInteger age ;
@property (nonatomic , copy) NSString * name ;
@end
//
// Person.m
//
// Created by 瞿杰 on 2017/8/16.
// Copyright © 2017年 iThinkerYZ. All rights reserved.
//
#import "Person.h"
@implementation Person
@end
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@end
#import "ViewController.h"
#import "Person.h"
@interface ViewController ()
@property (nonatomic , strong) Person * person ;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.person = [[Person alloc] init];
self.person.age = 1 ;
[self.person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
}
-(void)dealloc
{
[self.person removeObserver:self forKeyPath:@"age"];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// 实际调用了 setAge: 方法
self.person.age++ ;
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
NSLog(@"最新年龄:%ld , 类名:%@",self.person.age,NSStringFromClass([self.person class]));
}
@end
2.使用 self.person->_age++ 时 KVO 不会调用 observe 对象的 observeValueForKeyPath:…… 方法。
把 Person 对象的 age 属性(默认是 @protected 妨问权限)转变成 @public 的权限
// // Person.h // // Created by 瞿杰 on 2017/8/16. // Copyright © 2017年 qujie. All rights reserved. // #import <Foundation/Foundation.h> @interface Person : NSObject { // 默认是私有变量,把它变成公共变量,外面就可以用 ->_age 这样的格式调用该对象属性,不然会报错 @public NSInteger _age ; } @property (nonatomic , assign) NSInteger age ; @property (nonatomic , copy) NSString * name ; @end
// // Person.m // // Created by 瞿杰 on 2017/8/16. // Copyright © 2017年 qujie. All rights reserved. // #import "Person.h" @implementation Person @end
在 ViewController 中注意第 46 行代码的使用如下
// // ViewController.m // // Created by 瞿杰 on 2017/8/16. // Copyright © 2017年 qujie. All rights reserved. // #import "ViewController.h" #import "Person.h" #import "NSObject+KVO.h" @interface ViewController () @property (nonatomic , strong) Person * person ; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.person = [[Person alloc] init]; self.person.age = 1 ; [self.person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil]; } -(void)dealloc { [self.person removeObserver:self forKeyPath:@"age"]; } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { // // 实际调用了 setAge: 方法 // self.person.age++ ; // 实际就只是单纯的赋值 self.person->_age++ ; } -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { NSLog(@"最新年龄:%ld , 类名:%@",self.person.age,NSStringFromClass([self.person class])); } @end
结果却是不会调下面的方法
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { NSLog(@"最新年龄:%ld , 类名:%@",self.person.age,NSStringFromClass([self.person class])); }
3.自定义 KVO
以上面的代码为例,系统KVO的实现过程
- 首先在添加观察者时,用runtime动态创建了一个子类 NSKVONotifying_Person,并修改 self.person 的 isa 指针指向该子类
- 再用 runtime 动态添加self.person 对象的一些属性如 observe 观察者(strong 强引用,这也解释了为什么每个 dealloc 方法内需要移除监听 )
- 当设置 self.person.age 时调用 setter 方法触发监听,然后就有了调用 oberve 的方法-(void)observeValueForKeyPath: ……
实现自定义KVO
Person 类
// // Person.h // // Created by 瞿杰 on 2017/8/16. // Copyright © 2017年 qujie. All rights reserved. // #import <Foundation/Foundation.h> @interface Person : NSObject @property (nonatomic , assign) NSInteger age ; @property (nonatomic , copy) NSString * name ; @end
// // Person.m // // Created by 瞿杰 on 2017/8/16. // Copyright © 2017年 qujie. All rights reserved. // #import "Person.h" @implementation Person @end
创建 NSObject 分类 NSObject+KVO 实现添加观察者方法:
// // NSObject+KVO.h // // Created by 瞿杰 on 2017/8/16. // Copyright © 2017年 qujie. All rights reserved. // #import <Foundation/Foundation.h> @interface NSObject (KVO) // 添加一个前缀为区分与系统不一样方法 -(void)qj_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context ; @end
实现 NSObject+KVO 对象方法
// // NSObject+KVO.m // // Created by 瞿杰 on 2017/8/16. // Copyright © 2017年 qujie. All rights reserved. // #import "NSObject+KVO.h" // runtime 需要的头文件 #import <objc/message.h> @implementation NSObject (KVO) -(void)qj_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context { // 1. 设置该方法的 当前对象的 isa 指针指向动态创建的 QJKVONotifying_Person 类 Class subClass = [self createClassWithClassName:"QJKVONotifying_Person" superClassName:[self class]]; object_setClass(self, subClass); // 2. 动态添加属性 observer 、keyPath 等 // OBJC_ASSOCIATION_RETAIN_NONATOMIC 即在 RAC 环境下等同于 @property (nonatomic , strong) [observer class] * observer ; objc_setAssociatedObject(self, "observer", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); objc_setAssociatedObject(self, "keyPath", keyPath, OBJC_ASSOCIATION_COPY_NONATOMIC); objc_setAssociatedObject(self, "options", @(options), OBJC_ASSOCIATION_RETAIN_NONATOMIC); objc_setAssociatedObject(self, "context", (__bridge id)(context), OBJC_ASSOCIATION_RETAIN_NONATOMIC); // 3. 方法交换 // 3.1 获取属性的 setter 方法名 NSString * methodName = [NSString stringWithFormat:@"set%@%@:",[[keyPath substringToIndex:1] uppercaseString],[keyPath substringFromIndex:1]]; // 3.2 跟据方法名 获取 方法 Method InstanceMethod1 = class_getInstanceMethod([self class], NSSelectorFromString(methodName)); // 3.3 把 setAgeMethod: 方法包装成 Method 类型 Method InstanceMethod2 = class_getInstanceMethod([self class], @selector(setAgeMethod:)); // 3.4 两个方法交换 method_exchangeImplementations(InstanceMethod1, InstanceMethod2); } -(void)setAgeMethod:(NSInteger)age { // 1.取出之前动态添加属性对应的值 NSObject * observer = objc_getAssociatedObject(self, "observer"); NSString * keyPath = objc_getAssociatedObject(self, "keyPath"); void * context = (__bridge void *)(objc_getAssociatedObject(self, "context")); NSKeyValueObservingOptions options = [objc_getAssociatedObject(self, @"options") integerValue]; // 2.取出 keyPath 先前的值 NSInteger oldAge = [[self valueForKeyPath:keyPath] integerValue]; // 3.因为 setAgeMethod: 方法 与 方法名为 methodName 的方法交换了,所以不会告成循环 [self setAgeMethod:age]; // 4. 调用 KVO 监听的方法 if ([observer respondsToSelector:@selector(observeValueForKeyPath:ofObject:change:context:)]) { NSMutableDictionary * changes = [NSMutableDictionary dictionary]; [changes setValue:keyPath forKey:@"keyPath"]; // 4.1通过操类型来设置字典的值 if (options == NSKeyValueObservingOptionOld) { [changes setValue:@(oldAge) forKey:NSKeyValueChangeOldKey]; } else if (options == NSKeyValueObservingOptionNew){ [changes setValue:@([[self valueForKeyPath:keyPath] integerValue]) forKey:NSKeyValueChangeNewKey]; }// 接下来的枚举就不再写了 // 4.2 调用 observer 观察者实现的 KVO 方法 [observer observeValueForKeyPath:keyPath ofObject:self change:changes context:context]; } } /** 跟据类名 className 和 父类名 superClassName 创建名为 className 的类 */ -(Class)createClassWithClassName:(const char * )className superClass:(Class)superClass { // 1.先查找项目中是否存在这个类 Class subClass = objc_getClass(className); // 2.如果不存在则动态创建 if (!subClass) { // 2.1 根据父类来创建当前的子类 subClass = objc_allocateClassPair(superClass, className, 0); } return subClass ; } @end
在 ViewController 中的用法如下
// // ViewController.m // // Created by 瞿杰 on 2017/8/16. // Copyright © 2017年 qujie. All rights reserved. // #import "ViewController.h" #import "Person.h" #import "NSObject+KVO.h" @interface ViewController () @property (nonatomic , strong) Person * person ; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.person = [[Person alloc] init]; self.person.age = 1 ; // [self.person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil]; [self.person qj_addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil]; } -(void)dealloc { [self.person removeObserver:self forKeyPath:@"age"]; } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { // 实际调用了 setAge: 方法 self.person.age++ ; } -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { NSLog(@"最新年龄:%ld , 类名:%@",self.person.age,NSStringFromClass([self.person class])); } @end
运行结果如下