让人刮目相看的KVO

161 篇文章 2 订阅

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<NSKeyValueChangeKey,id> *)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 deallocatedwhile 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 observer <ObjectB 0x170017840> for the key path "valueA"from <ObjectA 0x170017830> 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 deallocatedwhile 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"from <ObjectA 0x600002bb0d30> because 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<NSKeyValueChangeKey,id> * _Nonnull change) {
        NSLog(@"ObjectA valueA changed:%@", change);
    }];
    self.objectA.valueA = 20;
    self.objectA = nil;
    self.objectB = nil;

这是小编的iOS开发交流群:624212887,里面都是iOS开发,全栈发展,欢迎入驻交流!——点击:加入

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值