美文网首页iOS进阶Fuck iOS EveryDay
iOS KVO 实现原理 和 自己实现KVO

iOS KVO 实现原理 和 自己实现KVO

作者: LikeSomeBody | 来源:发表于2017-12-30 14:47 被阅读1027次

一:前言

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 系统这个对象的类是什么 ) 指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例。

深入的分析 可以参考 https://www.jianshu.com/p/e59bb8f59302  这篇文章中的 内容如下:

①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);

3.0  测试失败 使用KVC 获取集合对象  知道的同学可以  告诉我下

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

    // 这里造成了crash

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

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

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

详细的过程请参考 http://tech.glowing.com/cn/implement-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];

}

经过 测试 无论 如何 添加还是删除 都不会 crash  移除后 也没有内存泄漏 !!

但是 还是 出现了问题   如果 被观察者的属性 是 基本数据类型  例如 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)

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

参考文章:

https://www.jianshu.com/p/e59bb8f59302

http://tech.glowing.com/cn/implement-kvo/

https://www.mikeash.com/pyblog/friday-qa-2010-11-6-creating-classes-at-runtime-in-objective-c.html

相关文章

  • iOS - KVO

    [toc] 参考 KVO KVC 【 iOS--KVO的实现原理与具体应用 】 【 IOS-详解KVO底层实现 】...

  • iOS 自定义KVO

    自己实现kvo之前,需要知道iOS系统对kvo的实现。 系统实现kvo的原理 这依赖了OC强大的runtime特性...

  • IOS底层(三) KVO底层实现原理

    @[TOC](IOS底层(三) KVO底层实现原理 ) 一,KVO简述 KVO的全称 Key-Value Obse...

  • iOS KVO 实现原理 和 自己实现KVO

    一:前言 KVO 是我们经常使用的键值观察者模式的一种实现 。大概功能是 比如有两个对象 A 和B B 观察了A的...

  • iOS KVO

    KVO 示例 KVO的实现原理

  • iOS探索KVO实现原理,重写KVO

    写响应式编程博客时,提到了KVO,今天我们探索一下KVO的实现原理及如何自己实现KVO功能 首先简单的KVO实现 ...

  • iOS面试题(4) KVO KVC

    声明,不是原创,笔记均来自 群主大神~ 手动实现KVO 什么是KVO和KVC? KVO内部实现原理 NSNotif...

  • iOS-底层原理-自定义KVO

    1.自定义KVO 1.上一篇博客了解了iOS 系统KVO的底层实现原理,那么这里进行自定义KVO,更好的理解原理和...

  • iOS 自定义KVO

    通过在了解KVO的实现原理和实现步骤之后,我们可以手动实现KVO,具体可以看最后的demo,这里只讲实现原理 添加...

  • iOS原理篇(一): KVO实现原理

    KVO实现原理 什么是 KVO KVO 基本使用 KVO 的本质 总结 一 、 什么是KVO KVO(Key-Va...

网友评论

    本文标题:iOS KVO 实现原理 和 自己实现KVO

    本文链接:https://www.haomeiwen.com/subject/hcuggxtx.html