相信kvo 作为一个ios开发者都用过,但是kvo底层具体怎么实现的,相信很多人也不太明白或者就算有的人明白 但是也说的不是很清楚,我开始也不太明白,自己打算研究一下,下面我来分享一下,如果有错误希望大家给予指正:
将oc的代码转换成c或者c++代码的命令(比如我转换的main.m文件)
首先切换到main.m所在的文件位置,然后执行这段命令:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
如果代码中有__weak
执行这段命令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
- kvo 是什么?作用?
kvo 是键值观察者,key value observer 的缩写,作用是用来观察对象的属性改变的。 - kvo的简单使用,下面我沾上我的代码,里面不经常用的参数,我做了一一的说明
#import "ViewController.h"
#import "DGPerson.h"
#import <objc/runtime.h>
@interface ViewController ()
@property (strong, nonatomic) DGPerson *person1;
//@property (strong, nonatomic) DGPerson *person2;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 动态生成这个类isa ---- NSKVONotifying_DGPerson
self.person1 = [[DGPerson alloc] init];
// 因为没有添加观察者 所以不生成中间类 而是isa --- 指向 DGPerson
self.person1.age = 10;
// self.person2 = [[DGPerson alloc] init];
// self.person2.age = 20;
// 查看他们调用那一个方法
// NSLog(@"调用之前的 调用方法的地址 :%p %p",[self.person1 methodForSelector:@selector(setAge:)],[self.person2 methodForSelector:@selector(setAge:)]);
// 添加观察者。观察者的主要目的是 观察对象的属性的改变
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"age" options:options context:@"sasd"];
#pragma mark - 添加了观察者的之后的方法调用foundation的这个方法_NSSetIntValueAndNotify
// P (IMP) 0x10daf2790 :通过地址打印调用的方法
// NSLog(@"调用之后的 调用方法的地址 :%p %p",[self.person1 methodForSelector:@selector(setAge:)],[self.person2 methodForSelector:@selector(setAge:)]);
NSLog(@"asdahsdajsdajsd");
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.person1.age = 20;
// self.person2.age = 30;
}
/**
观察者的回去调用的代理
@param keyPath 那个属性
@param object 那个对象
@param change 改变的字典
@param context 传递的数据
*/
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"%@ --- 的 %@ 属性值发生了改变,变化为 %@ 传递给观察者的参数 %@",object,keyPath,change,context);
}
-(void)dealloc {
[self.person1 removeObserver:self forKeyPath:@"age"];
}
@end
解释:其中注释的地方 大家不要看 ,先看没有注释的地方,可以看到我们改变age的值开始调用了observeValueForKeyPath。。。。。的这个方法,其中observeValueForKeyPath这个方法的参数我在注释的时候都做了说明,特别注意的是context:苹果的官方文档的解释是:注册观察者以接收键值观察通知时提供的值,其实我们可以简单的理解为传递的参数数据。
以上的打印信息为:
2018-07-04 16:52:42.828077+0800 kvo的底层原理窥探[2581:264281] <DGPerson: 0x600000018440> --- 的 age 属性值发生了改变,变化为 {
kind = 1;
new = 20;
old = 10;
}
-
kvo本质使用不是我们今天的重点,我们的重点是研究kvo 到底是怎么一回事.
1.重新创建一个person对象,命名为person2 ,其中person2不添加观察者, 然后我们看看他们的isa指针指向的是什么?
image.png
我们大胆的猜测 也就是添加了person1 添加了观察者,添加了观察者的对象他会在运行的时候时候动态生成一个中间类NSKVONotifying_DGPerson,怎么生成的,因为我们的oc 是一门动态的语言,对象添加了观察者,在运行时候 runtime会动态给他生成一个中间类。
2.我们在我们的app中创建一个类 名字就叫NSKVONotifying_DGPerson,运行程序 发现观察者失败了:
image.png
这也间接证明了,确实他在运行的时候创建了一个NSKVONotifying_DGPerson,其中我的person对象是DGPerson,你自己的项目他创建的肯定和你的类名字一至的,我们知道了 他创建一个类 他创建了什么方法,怎么执行的呢?
3.执行顺序,其中我们在添加观察者前后来看下他的调用方法的内存地址,通过方法的内存地址我们来在lldb中输入命令 来看他执行的方法(p (IMP) 内存地址),通过输入命令 我们可以看到
image.png
他调用的是这个方法:_NSSetIntValueAndNotify 这个方法一看就是c语言的方法,那我们可以大致猜一下,在这个类中NSKVONotifying_DGPerson 他的底层怎么实现的
#import "NSKVONotifying_DGPerson.h"
@implementation NSKVONotifying_DGPerson
- (void)setAge:(int)age
{
_NSSetIntValueAndNotify();
}
// 伪代码
void _NSSetIntValueAndNotify()
{
[self willChangeValueForKey:@"age"];
[super setAge:age];
[self didChangeValueForKey:@"age"];
}
- (void)didChangeValueForKey:(NSString *)key
{
// 通知监听器,某某属性值发生了改变
[oberser observeValueForKeyPath:key ofObject:self change:nil context:nil];
}
解释:其中 以上的代码是我估计的,其实苹果是闭元的,我们看不到具体怎么实现,但是如果你的手机是越狱的 可以通过ifunbox这个软件来找到fundation框架中的这个方法,确实有的 我找过 因为现在的我手机不是越狱的 我没法演示了,通过反汇编的软件来看到他的汇编的代码,那个软件可以生成伪代码,她的伪代码和我写的基本差不多。
- 下面我们来看一个现象,我写了一句这样的代码
Class person1Class = object_getClass(self.person1);
Class person1Cls = [self.person1 class];
NSLog(@"%@ --- %@",person1Class,person1Cls);
Class person2Class = object_getClass(self.person2);
Class person2Cls = [self.person2 class];
NSLog(@"%@ --- %@",person2Class,person2Cls);
发生现象:
image.png
解释:要明白这个,需要知道这个函数object_getClass的作用,她的作用是:
1.传递一个实例对象生成的是类对象
2.传递一个类对象生成的是原类对象
3.传递原类对象生成的是基类的原类对象
以上的打印信息,可以证明添加了观察这的对象他在这个NSKVONotifying_DGPerson类中重写了class方法,为什么呢 因为如果不重写 那么我们的调用[self.person1 class]这个方法 应该返回的是:NSKVONotifying_DGPerson而不是DGPerson,我猜测苹果这样写 就是为了让我们普通开发者不知道NSKVONotifying_DGPerson,为什么object_getClass 就能返回他本身的类呢?因为object_getClass 是runtime的方法,他拿到的就是我们真实的方法。怎么证明NSKVONotifying_DGPerson确实重写了class方法呢?我用下面的代码进行证实。
- (void)methodWithClass:(Class)cls {
unsigned int count;
Method *methodList = class_copyMethodList(cls, &count);
NSMutableString *nameStr = [NSMutableString string];
for (NSInteger index = 0; index < count; index ++) {
Method method = methodList[index];
NSString *methodName = NSStringFromSelector(method_getName(method));
[nameStr appendFormat:@" + %@",methodName];
}
NSLog(@"nameStr --- %@",nameStr);
// c语言的函数要释放
free(methodList);
}
打印:
image.png
可以看到 其实他是实现了四个方法 其中有class,我们大致猜测一下 ,class的实现应该是这样的
- (Class)class{
return [DGPerson class];
}
- 怎样手动触发kvo?
大致思路是:我们首先禁止kvo自动发送通知的方法,二是我们需要手动调用willChangeValuesForKey:(NSString *)key;和didChangeValuesForKey::(NSString *)key的方法;代码如下
#import "DGPerson.h"
@implementation DGPerson
-(void)setAge:(int)age {
if (age < 0) {
_age = age;
}else{
[self willChangeValueForKey:@"age"];
_age = age;
[self didChangeValueForKey:@"age"];
}
}
/**
是否自动发送通知呢
@return no:不自动发送通知
*/
+(BOOL)automaticallyNotifiesObserversOfAge{
return NO;
}
@end
注意:需要添加观察者 ,不添加观察者无效
- kvo删除尽量不要用keypath这种方式进行删除,因为如果我是DGStudent继承的DGPerson 我在DGPerson添加了观察者,我在DGStudent也添加了观察者,那么我dealloc 都进行了删除,那么删除的同一个keypath的时候就会crash。
- 其中DGStudent 继承 DGPerson,在DGStudent添加了观察者,在DGPerson中也添加了观察者,那么只会调用 DGStudent中的观察者,其中在oneViewController 中添加了观察者,也在viewController中添加了观察者,那么在oneViewController中进行调用,要想调用父类中的观察者回调,需要这样书写:
#import "DGTwoViewController.h"
#import "DGStudent.h"
@interface DGTwoViewController ()
@end
@implementation DGTwoViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.student addObserver:self forKeyPath:@"weight" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"weight"];
[self.student addObserver:self forKeyPath:@"height" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"height"];
}
/**
观察者的回去调用的代理
@param keyPath 那个属性
@param object 那个对象
@param change 改变的字典
@param context 传递的数据
*/
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"------------------");
NSLog(@"%@ --- 的 %@ 属性值发生了改变,变化为 %@ 传递给观察者的参数 %@",object,keyPath,change,context);
if (([object isKindOfClass:[DGStudent class]]) && ([keyPath isEqualToString:@"weight"] || [keyPath isEqualToString:@"height"])) {
NSLog(@"我们是好孩子");
}else{
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
#pragma mark - 其他的相关的处理
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.student.weight = 50;
self.student.height = 50;
}
-(void)dealloc {
[self.student removeObserver:self forKeyPath:@"age"];
}
@end
头文件:
#import "DGViewController.h"
@interface DGTwoViewController : DGViewController
@end
网友评论