摘要:但同时在使用的过程中有许多需要特变注意的地方,稍有不慎就会导致崩溃,不得不让人刮目相看。到底是怎么回事儿呢,下面根据个人的使用情况一一道来。所以添加很删除观察者应该成对出现,互相匹配,才能保证使用的正确稳定性。
KVO在OC中是实现键值(key-value-observing)观察的方式,在设计模式中是典型的观察者模式,当被观察者的键值发生改变时会通知到事先添加的观察者,在app开发中经常被使用,达到事半功倍的效果。但同时KVO在使用的过程中有许多需要特变注意的地方,稍有不慎就会导致app崩溃,不得不让人刮目相看。到底是怎么回事儿呢,下面根据个人的使用情况一一道来。
使用KVO定义2个NSObject子类对象ObjectA, ObjectB,并分别添加valueA和valueB的属性
@interface ObjectA : NSObject @property (nonatomic, assign) NSInteger valueA; @end @interface ObjectB : NSObject @property (nonatomic, assign) NSInteger valueB; @end
用ObjectB的对象实例objectB来观察ObjectA实例的valueA的变化,当发生变化打印对象的新值
@implementation ObjectB - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void *)context { if(![object isKindOfClass:[ObjectA class]]) { return; } if(![keyPath isEqualToString:@"valueA"]) { return; } NSLog(@"ObjectA valueA changed:%@", change); } @end
self.objectA = [ObjectA new]; self.objectB = [ObjectB new]; [self.objectA addObserver:self.objectB forKeyPath:@"valueA" options:NSKeyValueObservingOptionNew context:nil]; self.objectA.valueA = 20; [self.objectA removeObserver:self.objectB forKeyPath:@"valueA"];
执行后objectA的valueA被修改为20的时候,观察者objectB会得到通知并打印其变化:
2018-11-02 10:11:08.867329+0800 KVOTestDemo[485:73437] ObjectA valueA changed:{ kind = 1; new = 20; }KVO原理
KVO的实现是基于iOS runtime机制的isa-swizzling,当一个对象的属性被注册观察者时,会生成一个中间类继承自此类,然后将类的isa指针指向新生成的子类,这样被观察的对象就变成了这个中间类,同时重写了属性的setter方法,当新对象的属性发生变化时,则会依次通知注册的观察者对象。
苹果在这里给出了简单解释
连续对objectA同一属性valueA添加观察者objectB是可以的,但是也要保证在移除观察者的时候也要移除2次,不然可能会引发崩溃,因为不同iOS系统版本表现不一致,后面会提到:
//重复添加观察者 self.objectA = [ObjectA new]; self.objectB = [ObjectB new]; [self.objectA addObserver:self.objectB forKeyPath:@"valueA" options:NSKeyValueObservingOptionNew context:nil]; [self.objectA addObserver:self.objectB forKeyPath:@"valueA" options:NSKeyValueObservingOptionNew context:nil]; self.objectA.valueA = 20; [self.objectA removeObserver:self.objectB forKeyPath:@"valueA"]; [self.objectA removeObserver:self.objectB forKeyPath:@"valueA"]; self.objectB = nil; self.objectA = nil;
观察者会被调用2次:
2018-11-03 16:34:08.492202+0800 KVOTestDemo[972:235154] ObjectA valueA changed:{ kind = 1; new = 20; } 2018-11-03 16:34:08.492281+0800 KVOTestDemo[972:235154] ObjectA valueA changed:{ kind = 1; new = 20; }
移除的观察者需要移除2次,不然会引发崩溃:
//重复添加观察者 self.objectA = [ObjectA new]; self.objectB = [ObjectB new]; [self.objectA addObserver:self.objectB forKeyPath:@"valueA" options:NSKeyValueObservingOptionNew context:nil]; [self.objectA addObserver:self.objectB forKeyPath:@"valueA" options:NSKeyValueObservingOptionNew context:nil]; self.objectA.valueA = 20; [self.objectA removeObserver:self.objectB forKeyPath:@"valueA"]; // [self.objectA removeObserver:self.objectB forKeyPath:@"valueA"]; self.objectB = nil; self.objectA = nil;
在objectA销毁时因为还存在观察者而导致崩溃
2018-11-03 16:29:31.139120+0800 KVOTestDemo[958:233655] *** Terminating app due to uncaught exception "NSInternalInconsistencyException", reason: "An instance 0x17001d720 of class ObjectA was deallocated while key value observers were still registered with it.删除不存在的观察者
//移除不存在的观察者 self.objectA = [ObjectA new]; self.objectB = [ObjectB new]; self.objectA.valueA = 20; [self.objectA removeObserver:self.objectB forKeyPath:@"valueA"];
objectA并没有添加objectB为观察者,而直接去移除其观察者会导致崩溃。
2018-11-03 16:39:47.369455+0800 KVOTestDemo[979:236927] *** Terminating app due to uncaught exception "NSRangeException", reason: "Cannot remove an observerfor the key path "valueA" from because it is not registered as an observer."
所以添加很删除观察者应该成对出现,互相匹配,才能保证KVO使用的正确稳定性。
被观察者销毁时还存在观察者//被观察者销毁时还存在有未移除的观察者 self.objectA = [ObjectA new]; self.objectB = [ObjectB new]; [self.objectA addObserver:self.objectB forKeyPath:@"valueA" options:NSKeyValueObservingOptionNew context:nil]; self.objectA.valueA = 20; self.objectA = nil;
此例中,objectA添加了观察者objectB,但是直到objectA销毁时也没有移除此观察者,测试在iOS10及其之前系统会导致崩溃,但是iOS11后系统做了兼容,所以并不会崩溃。
iOS10上面的崩溃如下:
2018-11-03 17:05:42.101695+0800 KVOTestDemo[989:241126] *** Terminating app due to uncaught exception "NSInternalInconsistencyException", reason: "An instance 0x17001f8d0 of class ObjectA was deallocated while key value observers were still registered with it.
这点值得注意,因为开发者往往在较高的iOS系统上面开发测试,而忽略了不同版本之间的差异,或者系统覆盖测试不完全,则可能导致APP崩溃。在ARC开发中开发者可能越来越少的去关注对象释放的时机,如果被观察的对象提前于观察者释放同样可能导致崩溃。
移除一个已经销毁的观察者这种情况等同于移除一个非观察者对象,同样都会导致崩溃:
//移除一个已经销毁的观察者 self.objectA = [ObjectA new]; self.objectB = [ObjectB new]; [self.objectA addObserver:self.objectB forKeyPath:@"valueA" options:NSKeyValueObservingOptionNew context:nil]; self.objectA.valueA = 20; self.objectB = nil; [self.objectA removeObserver:self.objectB forKeyPath:@"valueA"]; self.objectA = nil;
出现崩溃:
2018-11-03 17:11:20.322089+0800 KVOTestDemo[40637:2785015] *** Terminating app due to uncaught exception "NSRangeException", reason: "Cannot remove an observer <(null) 0x0> for the key path "valueA" frombecause it is not registered as an observer."
所以一个对象如果作为观察者,那么在该对象dealloc前应当被移除。
总结1.KVO在使用时添加观察者和移除观察者应到成对出现
2.被观察者在销毁前应当移除所有的观察者,iOS10以下会崩溃,iOS11以上不会崩溃,坑点!
3.一个对象如果作为观察者,在该对象dealloc前应当被移除,否则会导致崩溃
看吧KVO真是让人刮目相看,看似功能强大,使用简单,但却暗藏杀机,稍有不慎便会导致APP崩溃,那么如何安全的使用KVO呢?
不妨试试Facebook的开源库KVOController
//FBKVOController使用起来更安全更简单 self.objectA = [ObjectA new]; self.objectB = [ObjectB new]; [self.objectB.KVOController observe:self.objectA keyPath:@"valueA" options:NSKeyValueObservingOptionNew block:^(id _Nullable observer, id _Nonnull object, NSDictionary* _Nonnull change) { NSLog(@"ObjectA valueA changed:%@", change); }]; self.objectA.valueA = 20; self.objectA = nil; self.objectB = nil;
以上问题都迎刃而解啦!测试demo在这里Github
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/17183.html
摘要:有没有什么更优雅,无感知的接入方式点此查看原文自修复技术实现与原理解析前言前言设计非常不合理,于是有很多的三方库,比如用更优的来规避这些,但是侵入性比较大,必须编码规范来约束所有人都要使用该方式。时及时移除时,让移除。 摘要: 【前言】KVO API设计非常不合理,于是有很多的KVO三方库,比如 KVOController 用更优的API来规避这些crash,但是侵入性比较大,必须编码...
摘要:它提供了一个简单现代的,同时也是线程安全的。它有如下优点使用自定义或者回调进行通知观测者移除时无异常控制器时隐式的观测者移除提升使用的性能线程安全并提供在观测者恢复时额外的保护更多关于的信息,可浏览的文档用法项目中有把用于和的示例程序。 KVOController 是一个简单安全的 KVO(Key-value Observing,键-值 观察)工具,用于 iOS 和 OS X 应用开发...
摘要:和不同,这种色彩空间有三个元素。我们要做一个用来改变这些值的滑块和一个显示颜色的方块区域。不论何时设置的值,我们需要让三个中与其相关的成员以及属性都要得到通知以保持一致。这一点这在中很重要。的子类拥有对象作为其属性。 欢迎大家关注我的公众号,我会定期分享一些我在项目中遇到问题的解决办法和一些iOS实用的技巧,现阶段主要是整理出一些基础的知识记录下来 showImg(https://se...
摘要:招聘一个靠谱的参考答案下说明面试题来源是微博我就叫怎么了的这篇博文招聘一个靠谱的,其中共题,除第一题为纠错题外,其他道均为简答题。答案为微博程序犭袁整理,未经出题者校对,如有纰漏,请向微博程序犭袁指正。 《招聘一个靠谱的 iOS》—参考答案(下) 说明:面试题来源是微博@我就叫Sunny怎么了的这篇博文:《招聘一个靠谱的 iOS》,其中共55题,除第一题为纠错题外,其他54道均为简答题...
摘要:用表示,返回值和参数之间用隔开,对象用表示。因为方法中返回值和参数之间必然有发起者和隔着,所以自然而然就成了返回值和参数之间的分隔符。 知识点概述 1.KVO实现原理2.runtime使用 目的 给NSObject添加一个Category,用于给实例对象添加观察者,当该实例对象的某个属性发生变化的时候通知观察者。 大体思路 添加观察者的方法中 - (void)SQ_addObserve...
阅读 4109·2023-04-25 19:30
阅读 1932·2023-04-25 15:09
阅读 2452·2021-11-16 11:45
阅读 1977·2021-11-15 18:07
阅读 1295·2021-11-11 17:22
阅读 1951·2021-11-04 16:06
阅读 3416·2021-10-20 13:47
阅读 2770·2021-09-22 16:03