KVO原理解析
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在添加监听之前以及之后的类对象
1 NSLog(@"%@", object_getClass(self.person)); 2 [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; 3 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内部的实现过程如下:
1. `-[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:]: 2. -[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:usingBlock:]: 3. [ZJPerson setName:]; 4. `NSKeyValueDidChange: 5. `NSKeyValueNotifyObserver: 6. - (void)observeValueForKeyPath:ofObject:change:context
简化成伪代码如下:
1 - (void)setName:(NSString *)name{ 2 _NSSetObjectValueAndNotify(); 3 } 4 5 void _NSSetObjectValueAndNotify { 6 [self willChangeValueForKey:@"name"]; 7 [super setName:name]; 8 [self didChangeValueForKey:@"name"]; 9 } 10 11 - (void)didChangeValueForKey:(NSString *)key{ 12 [observe observeValueForKeyPath:key ofObject:self change:nil context:nil]; 13 }
拓展》〉》NSKVONotifying_ZJPerson内部重写了方法?
利用runtime打印方法列表
1 unsigned int count; 2 Method *methods = class_copyMethodList(object_getClass(self.person), &count); 3 4 for (NSInteger index = 0; index < count; index++) { 5 Method method = methods[index]; 6 7 NSString *methodStr = NSStringFromSelector(method_getName(method)); 8 9 NSLog(@"%@\n", methodStr); 10 }
打印结果
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);
下面是打印方法列表以及成员变量列表
1 #pragma mark - Util 2 3 - (NSString *)copyMethodsByClass:(Class)cls{ 4 unsigned int count; 5 Method *methods = class_copyMethodList(cls, &count); 6 7 NSString *methodStrs = @""; 8 9 for (NSInteger index = 0; index < count; index++) { 10 Method method = methods[index]; 11 12 NSString *methodStr = NSStringFromSelector(method_getName(method)); 13 14 methodStrs = [NSString stringWithFormat:@"%@ ", methodStr]; 15 } 16 17 free(methods); 18 19 return methodStrs; 20 } 21 22 - (NSString *)copyIvarsByClass:(Class)cls{ 23 unsigned int count; 24 Ivar *ivars = class_copyIvarList(cls, &count); 25 26 NSMutableString *ivarStrs = [NSMutableString string]; 27 28 for (NSInteger index = 0; index < count; index++) { 29 Ivar ivar = ivars[index]; 30 31 NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)]; //获取成员变量的名字 32 33 NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)]; //获取成员变量的数据类型 34 35 [ivarStrs appendString:@"\n"]; 36 [ivarStrs appendString:ivarName]; 37 [ivarStrs appendString:@"-"]; 38 [ivarStrs appendString:ivarType]; 39 40 } 41 42 free(ivars); 43 44 return ivarStrs; 45 }
三、自己动手写一个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原理以及基本使用有更深的了解!!!