iOS - KVO 键值观察
1、KVO
KVO 是 Key-Value Observing 的简写,是键值观察的意思,属于 runtime 方法。Key Value Observing 顾名思义就是一种 observer 模式用于监听属性变量值的变化,也是运行时的方法,当实例变量改变时,系统会自动采取一些动作。KVO 跟 NSNotification 有很多相似的地方,用 addObserver:forKeyPath:options:context: 去 start observer, 用 removeObserver:forKeyPath:context 去 stop observer, 回调就是 observeValueForKeyPath:ofObject:change:context:。
对于 KVO 来说,我们要做的只是简单 update 我们的 property 的数据,不需要像 NSNotificationCenter 那样关心是否有人在监听你的请求,如果没有人监听该怎么办,所有 addObserver, removeObserver, callback 都是想要监听的你的 property 的 class 做的事情。曾经做个项目,用 NSNotificationCenter post Notification 在一个 network callback 里面,可是这时候因为最早的 addObserver 的 class 被释放了,接着生成的 addObserver 的 class, 就接受到了上一个 observer 该监听的事件,所以造成了错误,那时候的解决方案是为 addObserve key 做 unique,不会 2 次 addObserver 的 key 是相同的,但是有了 KVO, 我们同样可以用 KVO 来完成,当 addOberver 的的 object remove 的时候,就不会有这样的 callback 被调用了。
KVO 给我们提供了更少的代码,和 NSNotification 比好处,不需要修改被观察的 class, 永远都是观察你的人做事情。 但是 KVO 也有些毛病:
- 1、如果没有 observer 监听 keyPath, removeObsever:forKeyPath:context: 这个 keyPath, 就会 crash(崩溃), 不像 NSNotificationCenter removeObserver。
- 2、对代码你很难发现谁监听你的 property 的改动,查找起来比较麻烦。
- 3、对于一个复杂和相关性很高的 class,最好还是不要用 KVO, 就用 delegate 或者 notification 的方式比较简洁。
KVO 使用分三步:
- 1、注册成为观察者。
- 2、观察者定义 KVO 的回调。
- 3、移除观察者。
KVO 使用注意:
KVO 是同步的,一旦对象的属性发生变化,只有用同步的方式,才能保证所有观察者的方法能够执行完成。KVO 监听方法中,不要有太耗时的操作。
KVO 的方法调用,是在对应的线程中执行的。在子线程修改观察属性时,观察者回调方法将在子线程中执行。
在多个线程同时修改一个观察属性的时候,KVO 监听方法中会存在资源抢夺的问题,需要使用互斥锁。如果涉及到多线程,KVO 要特别小心,通常 KVO 只是做一些简单的观察和处理,千万不要搞复杂了,KVO的监听代码,一定要简单。
一定要删除观察者,如果不删除观察者,释放对象,会直接崩溃。An instance 0x7fd340ebc400 of class KvoClass was deallocated while key value observers were still registered with it.
在 Swift 中使用 KVO 的前提条件:
- 1、观察者和被观察者都必须是 NSObject 的子类,因为 OC 中 KVO 的实现基于 KVC 和 runtime 机制,只有是 NSObject 的子类才能利用这些特性;
- 2、观察的属性需要使用 dynamic 关键字修饰,表示该属性的存取都由 runtime 在运行时来决定,由于 Swift 基于效率的考量默认禁止了动态派发机制,因此要加上该修饰符来开启动态派发。
2、KVO 的使用
Objective-C
// KvoClass.h @interface KvoClass : NSObject @property(nonatomic, copy) NSString *name; @end // ViewController.m @property(nonatomic, retain) KvoClass *kvoObject; _kvoObject = [[KvoClass alloc] init];
Swift
// KvoClass.swift class KvoClass: NSObject { dynamic var name:String!
} // ViewController.swift var nameContext = "nameChange" var kvoObject = KvoClass()
2.1 KVO 添加
- (void)addObserver:(NSObject *)observer
forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(nullable void *)context;
public func addObserver(observer: NSObject,
forKeyPath keyPath: String,
options: NSKeyValueObservingOptions,
context: UnsafeMutablePointer<Void>)
参数说明:
第一个参数 observer 是观察的类;
第二个参数 keyPath 是被观察的类中被观察的属性;
第三个参数 options 是观察选项;
第四个参数 context 是传递的上下文内容。
Objective-C
// 添加观察者
[_kvoObject addObserver:self
forKeyPath:@"name"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:@"nameChange"]; // 改变被观察的键对应的值
_kvoObject.name = @"xiao bai";
sleep(2);
_kvoObject.name = @"xiao hei";
Swift
// 添加观察者
kvoObject.addObserver(self, forKeyPath:"name", options:[.New, .Old], context:&nameContext) // 改变被观察的键对应的值
kvoObject.name = "xiao bai"
sleep(2)
kvoObject.name = "xiao hei"
2.2 KVO 回调
- (void)observeValueForKeyPath:(nullable NSString *)keyPath
ofObject:(nullable id)object
change:(nullable NSDictionary<NSString*, id> *)change
context:(nullable void *)context;
public func observeValueForKeyPath(keyPath: String?,
ofObject object: AnyObject?,
change: [String : AnyObject]?,
context: UnsafeMutablePointer<Void>)
参数说明:
keyPath:监控的 key;
object:被监控的对象的基本属性;
change:被监控的对象的 key 对应的 value 值的变化(kind:类型,new:变化后的值,old:变化前的值。
Objective-C
// 系统自带方法
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context { if (context == @"nameChange") { NSLog(@"name 值被改变 kind = %@, oldValue = %@, newValue = %@",
change[@"kind"], change[@"old"], change[@"new"]); } else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
Swift
override func observeValueForKeyPath(keyPath: String?,
ofObject object: AnyObject?,
change: [String : AnyObject]?,
context: UnsafeMutablePointer<Void>) { if context == &nameContext { print("name 值被改变 kind = \(change![NSKeyValueChangeKindKey]),
oldValue = \(change![NSKeyValueChangeOldKey]),
newValue = \(change![NSKeyValueChangeNewKey])")
}
else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
2.3 KVO 移除
在实际工作中需要在合适的时候移除观察者身份。
NS_AVAILABLE(10_7, 5_0)
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath; public func removeObserver(observer: NSObject, forKeyPath keyPath: String, context: UnsafeMutablePointer<Void>)
public func removeObserver(observer: NSObject, forKeyPath keyPath: String) 参数说明:
第一个参数 observer 是观察的类;
第二个参数 keyPath 是被观察的类中被观察的属性;
第三个参数 context 是传递的上下文内容。
Objective-C
- (void)dealloc { // 移除观察者
[_kvoObject removeObserver:self forKeyPath:@"name" context:@"nameChange"];
}
Swift
deinit { // 移除观察者
kvoObject.removeObserver(self, forKeyPath:"name", context:&nameContext)
}
iOS - KVO 键值观察的更多相关文章
- K-V-O 键值观察机制
在两个不同的控制器之间传值是iOS开发中常有的情况,应对这种情况呢,有多种的应对办法.kvc就是其中的一种,所以,我们就在此解释之. key value observing 键值观察,给人一种高 ...
- KVO - 键值观察
[基本概念] 键值观察是一种使对象获取其他对象的特定属性变化的通知机制.控制器层的绑定技术就是严重依赖键值观察获得模型层和控制器层的变化通知的.对于不依赖控制器层类的应用程序,键值观察提供了一种简化的 ...
- KVO(键-值观察)
// 1.键-值观察 // 2.它提供一种机制,当指定的对象的属性被修改后,则对象就会接受到通知. // 3.符合KVC(Key-ValuedCoding)机制的对象才可以使用KVO // 4.实现过 ...
- KVO键值观察的具体实现
1.KVO简介 KVO是Objective-C对观察者设计模式的一种实现,它提供一种机制,指定一个被观察对象(如A类),当对象中的某个属性发生变化的时候,对象就会接收到通知,并作出相应的处理.在MVC ...
- OC键值观察KVO
什么是KVO? 什么是KVO?KVO是Key-Value Observing的简称,翻译成中文就是键值观察.这是iOS支持的一种机制,用来做什么呢?我们在开发应用时经常需要进行通信,比如一个model ...
- 09 (OC)* 键路径(keyPath)、键值编码(KVC)、键值观察(KVO)
键路径在一个给定的实体中,同一个属性的所有值具有相同的数据类型.键-值编码技术用于进行这样的查找—它是一种间接访问对象属性的机制. - 键路径是一个由用点作分隔符的键组成的字符串,用于指定一个连接在一 ...
- 路径(keyPath)、键值编码(KVC)和键值观察(KVO)
键路径 在一个给定的实体中,同一个属性的所有值具有相同的数据类型. 键-值编码技术用于进行这样的查找—它是一种间接访问对象属性的机制. - 键路径是一个由用点作分隔符的键组成的字符串,用于指定一个连接 ...
- 深度理解Key-Value Observing 键值观察
前言 在上一阶段的开发过程中,我们大量使用了 KVO 机制,来确保页面信息的及时同步.也因此碰到了很多问题,促使我们去进一步学习 KVO 的相关机制,再到寻找更好的解决方案.鉴于 KVO 让人欲仙 ...
- [深入浅出Cocoa]详解键值观察(KVO)及其实现机理
一,前言 Objective-C 中的键(key)-值(value)观察(KVO)并不是什么新鲜事物,它来源于设计模式中的观察者模式,其基本思想就是: 一个目标对象管理所有依赖于它的观察者对象,并在它 ...
随机推荐
- windows win10上传文件到linux服务器
1.最直接当然使用终端secucrt和xshell putty之类的,然后使用sz rz 2.如果服务器端不支持sz rz可以使用scp命令,下面这个pscp.exe就是支持scp的,基于ssh,很好 ...
- 在子线程中使用runloop,正确操作NSTimer计时的注意点 三种可选方法
一直想写一篇关于runloop学习有所得的文章,总是没有很好的例子.游戏中有一个计时功能在主线程中调用: 1 + (NSTimer *)scheduledTimerWithTimeInterval:( ...
- node-webkit教程<>Native UI API 之Menu(菜单)
node-webkit教程(6)Native UI API 之Menu(菜单)1 前言... 2 6.1 Menu 概述... 3 6.2 menu api6 6.2.1 new Menu([o ...
- ECshop中TemplateBeginEditable 和后台编辑讲解
在ecshop的dwt文件里面经常发现有“<!-- TemplateBeginEditable name="doctitle" -->和<!-- #BeginLi ...
- Java 如何快速序列化
1,定义变量
- Java实现批量修改文件名称
import java.io.File; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; /** ...
- poj2482 Stars in Your Window
此题可用线段树或静态二叉树来做. 考虑用线段树: 很容易想到先限定矩形横轴范围再考虑在此纵轴上矩形内物品总价值的最大值. 那么枚举矩形横轴的复杂度是O(n)的,考虑如何快速获取纵轴上的最大值. 我们不 ...
- vim标准操作
主要技巧: ->普通模式负责所有光标定位能力.插入模式随时使用<esc>键回到普通模式并且尽量保持普通模式为主要的工作状态. -> hjkl四键负责光标的低速移动,如果还在使用 ...
- 源码安装python
编译安装新版本python 一般来说python是linux系统的标配,但是版本一般却很老,而系统上面的很多服务可能与老的python存在依赖关系,我们又不能直接卸载.所以一般,我们可以在一个单独的目 ...
- 模块"xxxx.dll"已加载,但对DllRegisterServer的调用失败,错误代码为 XXXXXXXXX
WIN7.WIN8 注册 卸载dll 报错: 模块"xxxx.dll"已加载,但对DllRegisterServer的调用失败,错误代码为 XXXXXXXXX 解决方法: 若为 ...