ios kvo 要引入_iOS KVO 实现原理 和 自己实现KVO

一:前言

KVO 是我们经常使用的键值观察者模式的一种实现 。大概功能是 比如有两个对象 A 和B  B 观察了A的某个属性E  ,当E发生变化的时候  B中收到回调 回调中 有新的 或者 旧的值 。 apple  原生给我们提供了这样的方式 。但是 其实系统提供的 KVO 是有很多不方便的地方例如  系统KVO 的问题  和 系统KVO 问题二  补充一点 重复添加 或者 重复移除KVO 都会直接造成 Crash 对开发者 非常不友好。 DEMO下载地址   https://gitee.com/DeLongYang/iOS_KVO。

二: 系统KVO 的实现原理当你观察一个对象时,一个新的类会动态被创建。这个类继承自该对象的原本的类,并重写了被观察属性的 setter 方法。自然,重写的 setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象值的更改。最后把这个对象的 isa 指针 ( isa 指针告诉 Runtime 系统这个对象的类是什么 ) 指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例。①NSKVONotifying_A 类剖析:在这个过程,被观察对象的 isa 指针从指向原来的 A 类,被 KVO 机制修改为指向系统新创建的子类 NSKVONotifying_A 类,来实现当前类属性值改变的监听;

所以当我们从应用层面上看来,完全没有意识到有新的类出现,这是系统“隐瞒”了对 KVO 的底层实现过程,让我们误以为还是原来的类。但是此时如果我们创建一个新的名为“NSKVONotifying_A”的类(),就会发现系统运行到注册 KVO 的那段代码时程序就崩溃,因为系统在注册监听的时候动态创建了名为 NSKVONotifying_A 的中间类,并指向这个中间类了。

(isa指针的作用:每个对象都有 isa 指针,指向该对象的类,它告诉 Runtime 系统这个对象的类是什么。所以对象注册为观察者时,isa 指针指向新子类,那么这个被观察的对象就神奇地变成新子类的对象(或实例)了。) 因而在该对象上对 setter 的调用就会调用已重写的 setter,从而激活键值通知机制。

—>我猜,这也是 KVO 回调机制,为什么都俗称KVO技术为黑魔法的原因之一吧:内部神秘、外观简洁。

②子类setter方法剖析:KVO 的键值观察通知依赖于 NSObject 的两个方法:willChangeValueForKey:和 didChangevlueForKey:,在存取数值的前后分别调用 2 个方法:

被观察属性发生改变之前,willChangeValueForKey:被调用,通知系统该 keyPath 的属性值即将变更;当改变发生后, didChangeValueForKey: 被调用,通知系统该 keyPath 的属性值已经变更;之后, observeValueForKey:ofObject:change:context: 也会被调用。且重写观察属性的 setter 方法这种继承方式的注入是在运行时而不是编译时实现的。

KVO 为子类的观察者属性重写调用存取方法的工作原理在代码中相当于:

笔者 为了验证其 实现过程 在 DEMO 中 新建了 如图a所示的

a

的一组 。 然后在 ViewController 中注册了 A 对象的KVO ,并没有触发 KVO 。 系统提示[general] KVO failed to allocate class pair for name NSKVONotifying_A, automatic key-value observing will not work for this class

也就是如果我们自己创建了这个 叫做NSKVONotifying_A 的类 那么系统无法实现这个KVO。 说明KVO的原理 确实动态创建了一个名称 为NSKVONotifying_A的类。

三 : 顺带提下 KVC

1.0  通过KVC 获取 和 设置 私有变量  哈哈- (void)testKVCGetPrivateProperty

{

// 我们测试了 可以获取私有变量

KVCObject *kvcObj = [[KVCObject alloc] init];

NSString *name = [kvcObj valueForKeyPath:@"name"];

NSString *privateObj = [kvcObj valueForKeyPath:@"privatePro"];

int number = [[kvcObj valueForKeyPath:@"number"] intValue];

NSLog(@"name is:%@ --- privateObj is:%@ number is:%d",name,privateObj,number);

[kvcObj setValue:@"Hello" forKeyPath:@"privatePro"];

// 通过KVC 我们也可以 设置私有的变量的属性

NSLog(@"new privateObj is %@",[kvcObj valueForKeyPath:@"privatePro"]);

}

2.0 通过KVC  获取 多层的属性// 我们测试一下 多层属性的获取

NSString *employ2Name = [employee1 valueForKeyPath:@"manager.employee2.name"];

NSLog(@"employee2 name is %@",employ2Name);KVC还提供了集合操作的方法,直接获取到集合属性的同时还能对其进行求和,取平均数,求最大最小值等操作,如下为求和操作,具体可以到苹果官方文档详细了解。

// 这里造成了crash

//    NSNumber *arrNumber = [manager valueForKeyPath:@"arrProperty.sum"];

//    NSLog(@"arrNumber is %@",arrNumber);

四:如何使用 Runtime 来自己实现 KVO

在 DEMO 中是   NSObject + KVO 这个分类 。

五:自定义实现的KVO 和 系统的对比

自定义的 KVO 的用法

第一步 : 注册KVO- (void)secondRegisteCustomKVO

{

//

if (!self.message) {

self.message = [[SecondMessage alloc] init];

}

NSString *key = NSStringFromSelector(@selector(text));

[self.message PG_addObserber:self forKey:key withBlock:^(id observingObject, NSString *observedKey, id oldValue, id newValue) {

NSLog(@"%@ . %@ is now:%@",observingObject,observedKey,newValue);

dispatch_async(dispatch_get_main_queue(), ^{

self.textField.text = newValue;

});

}];

[self onCustomKVOButtonClick:nil];

}

第二步 :移除KVO- (void)viewWillDisappear:(BOOL)animated

{

// 如果不移除掉的话会造成 内存泄漏

NSString *key = NSStringFromSelector(@selector(text));

NSLog(@"key is %@",key);

[self.message PG_removeObserver:self forKey:key];

}

但是 还是 出现了问题   如果 被观察者的属性 是 基本数据类型  例如 int ,float 等的类型。笔者 发现 原因出在 NSObject+KVO分类中的 自定义的 setter 方法#pragma mark ---- 很明显这个 setter 方法 和getter 方法 只是 写了id 类型 的没写 基础类型的 比如int float 等 笔者要加上这些类型 Thread 1: EXC_BAD_ACCESS (code=1, address=0x2)

static void kvo_setter(id self,SEL _cmd,id newValue)

如果 有朋友找到 了解决办法 欢迎 联系我 。

参考文章:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值