KVO 在我们项目开发中, 经常被用到, 但很少会被人关注, 但如果面试一些大公司, 针对 KVO 的面试题可能如下:
知道 KVO 嘛, 底层是怎么实现的?
如何动态的生成一个类?
可不可以自己写一个 KVO?
今天我们围绕上面几个问题, 我们先看 KVO 底层实现原理, 以及怎么自己写一个 KVO?
一, KVO
1. KVO 定义
KVO: 可以监听一个对象的某个属性是否发生了改变, 或者通知其他对象的指定属性发生了改变.
2.KVO 实现
2.1 监听某个对象的属性
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
2.2 实现协议
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary *)change context:(nullable void *)context;
2.3 移除监听
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
下面是一个简单的演示:
- - (void)viewDidLoad {
- [super viewDidLoad];
- // Do any additional setup after loading the view, typically from a nib.
- self.person = [[ZJPerson alloc] init];
- [self.person setName:@"zhangsan"];
- [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
- }
- - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
- [self.person setName:@"lisi"];
- }
- - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
- NSLog(@"%@", change);
- }
- - (void)dealloc{
- [self.person removeObserver:self forKeyPath:@"name"];
- }
运行结果
通过以上 demo, 我们来思考 KVO 为什么能监听到属性变化, 底层又是怎么样实现的呢?
3. KVO 底层实现
在查看 KVO 底层实现, 我们首先用 runtime 在添加监听之前以及之后的类对象
- NSLog(@"%@", object_getClass(self.person));
- [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
- NSLog(@"%@", object_getClass(self.person));
可以查看结果如下:
- 2018-05-19 22:48:18.726028+0800 KVO[33804:3059947] ZJPerson
- 2018-05-19 22:48:18.726535+0800 KVO[33804:3059947] NSKVONotifying_ZJPerson
通过上面发现, 添加监听之后, 实例对象的类对象发生了改变, 系统自动为我们动态添加了一个 NSKVONotifying_+ 类名的类, 改变属性的值是通过 setter 方法进行实现, 很明显是系统已经动态生成了 NSKVONotifying_ZJPerson 类, 并重写了 setter 方法, 所以不可以创建 NSKVONotifying_ZJPerson 类了, 如果创建了 NSKVONotifying_ZJPerson 类, 会报以下错误:
2018-05-19 22:56:32.223288+0800 KVO[33919:3068985] [general] KVO failed to allocate class pair for name NSKVONotifying_ZJPerson, automatic key-value observing will not work for this class
错误提示的是: 创建 NSKVONotifying_ZJPerson 失败.
那么问题又来了, 重写的 setter 方法内部又做了什么? 我们再次利用 runtime 打印下面方法的实现.
通过上面发现, 发现内部调用了 Foundation 框架的_NSSetObjectValueAndNotify 方法, 我们再次看看_NSSetObjectValueAndNotify 内部的实现过程如下:
- `-[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:]:
- -[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:usingBlock:]:
- [ZJPerson setName:];
- `NSKeyValueDidChange:
- `NSKeyValueNotifyObserver:
- - (void)observeValueForKeyPath:ofObject:change:context
简化成伪代码如下:
- - (void)setName:(NSString *)name{
- _NSSetObjectValueAndNotify();
- }
- void _NSSetObjectValueAndNotify {
- [self willChangeValueForKey:@"name"];
- [super setName:name];
- [self didChangeValueForKey:@"name"];
- }
- - (void)didChangeValueForKey:(NSString *)key{
- [observe observeValueForKeyPath:key ofObject:self change:nil context:nil];
- }
拓展NSKVONotifying_ZJPerson 内部重写了方法?
利用 runtime 打印方法列表
- unsigned int count;
- Method *methods = class_copyMethodList(object_getClass(self.person), &count);
- for (NSInteger index = 0; index <count; index++) {
- Method method = methods[index];
- NSString *methodStr = NSStringFromSelector(method_getName(method));
- NSLog(@"%@\n", methodStr);
- }
打印结果
- 2018-05-20 08:57:07.883400+0800 KVO[35888:3218908] setName:
- 2018-05-20 08:57:07.883571+0800 KVO[35888:3218908] class
- 2018-05-20 08:57:07.883676+0800 KVO[35888:3218908] dealloc
- 2018-05-20 08:57:07.883793+0800 KVO[35888:3218908] _isKVOA
二, 如何动态生成类
说到动态生成一个类, 也就是利用了苹果的 runtime 机制, 下面我们来动态创建生成类.
2.1 创建类
Class customClass = objc_allocateClassPair([NSObject class], "ZJCustomClass", 0);
2.2 添加实例变量
- // 添加实例变量
- class_addIvar(customClass, "age", sizeof(int), 0, "i");
2.3 添加方法, V@: 表示方法的参数和返回值
class_addMethod(customClass, @selector(hahahha), (IMP)hahahha, "V@:");
需要实现的方法:
- void hahahha(id self, SEL _cmd)
- {
- NSLog(@"hahahha====");
- }
- - (void)hahahha{
- }
然后注册到运行时环境
objc_registerClassPair(customClass);
下面是打印方法列表以及成员变量列表
- #pragma mark - Util
- - (NSString *)copyMethodsByClass:(Class)cls{
- unsigned int count;
- Method *methods = class_copyMethodList(cls, &count);
- NSString *methodStrs = @"";
- for (NSInteger index = 0; index < count; index++) {
- Method method = methods[index];
- NSString *methodStr = NSStringFromSelector(method_getName(method));
- methodStrs = [NSString stringWithFormat:@"%@", methodStr];
- }
- free(methods);
- return methodStrs;
- }
- - (NSString *)copyIvarsByClass:(Class)cls{
- unsigned int count;
- Ivar *ivars = class_copyIvarList(cls, &count);
- NSMutableString *ivarStrs = [NSMutableString string];
- for (NSInteger index = 0; index < count; index++) {
- Ivar ivar = ivars[index];
- NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)]; // 获取成员变量的名字
- NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)]; // 获取成员变量的数据类型
- [ivarStrs appendString:@"\n"];
- [ivarStrs appendString:ivarName];
- [ivarStrs appendString:@"-"];
- [ivarStrs appendString:ivarType];
- }
- free(ivars);
- return ivarStrs;
- }
三, 自己动手写一个 KVO
给 NSObject 添加一个 Category,NSObject+KVO 监听方法
3.1 .h 文件
- #import <Foundation/Foundation.h>
- @interface NSObject (MY_KVO)
- - (void)My_addObserver:(NSObject *_Nonnull)observer forKeyPath:(NSString *_Nonnull)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- @end
3.2 .m 实现文件
- #import "NSObject+MY_KVO.h"
- #import "MY_KVONotifying_Person.h"
- #import <objc/runtime.h>
- @implementation NSObject (MY_KVO)
- -(void)My_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{
- // 修改 isa 指针 (runtime) 系统的 MY_KVONotifying_Person 这个类是动态生成的, 我们直接手动创建
- object_setClass(self, [MY_KVONotifying_Person class]);
- // 给对象动态添加属性, 之前文章介绍过了. 目的是保存 observer, 好在 set 方法里面拿到, 调用
My_addObserver:forKeyPath:options:context: 这个方法
- objc_setAssociatedObject(self, (__bridge const void *)(keyPath), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- }
- @end
3.3 重写 MY_KVONotifying_Person set 方法
- #import "MY_KVONotifying_Person.h"
- #import "NSObject+MY_KVO.h"
- #import <objc/runtime.h>
- @implementation MY_KVONotifying_Person
- - (void)setAge:(int)age{
- id observer = objc_getAssociatedObject(self, @"age");
- if (observer && [observer respondsToSelector:@selector(My_addObserver:forKeyPath:options:context:)]) {
- [observer My_addObserver:observer forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
- }
- [super setAge:age];
- }
- @end
3.4 使用
- #import "ViewController.h"
- #import "Person.h"
- #import "NSObject+MY_KVO.h"
- @interface ViewController ()
- @property (nonatomic,strong)Person * p;
- @end
- @implementation ViewController
- - (void)viewDidLoad {
- [super viewDidLoad];
- Person * p =[[Person alloc] init];
- [p My_addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
- _p= p;
- }
- - (void)My_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{
- NSLog(@"age++ 自己的 KVO");
- }
- - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
- _p.age ++ ;
- }
注意点: 修改 Xcode 的一个配置, 设为 NO
以上就是 KVO 的基本内容, 希望通过本篇博客, 大家对 KVO 原理以及基本使用有更深的了解!!!
来源: https://www.cnblogs.com/guohai-stronger/p/9473551.html