KVO
在Swift中我们也是可以使用KVO的,但是仅限于在 NSObject
的子类中。这是可以理解的,因为KVO是基于KVC (Key-Value Coding)以及动态派发技术实现的,而这些东西都是Objective-C运行时的概念。另外由于Swift为了效率,默认禁用了动态派发,因此想用Swift来实现KVO, 我们还需要做额外的工作,那就是将想要观测的对象标记为 dynamic
。
在Swift中,为一个 NSObject
的子类实现KVO的最简单的例子看起来是这样的:
//swift中实现KVO (仅限于NSObject的子类),需将观测的对象标记为 dynamic 。
class MyClass: NSObject {
dynamic var date = Date()
}
private var myContext = 0
class Class: NSObject {
var myObject: MyClass!
override init() {
super.init()
myObject = MyClass()
print("初始化 MyClass, 当前日期为:\(myObject.date)")
myObject.addObserver(self,
forKeyPath: "date",
options: .new,
context: &myContext)
delay(3) { //delay方法是在GCD和延时调用一节实现的方法
self.myObject.date = Date()
}
}
override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?) {
if let change = change, context == &myContext {
let a = change[NSKeyValueChangeKey.newKey]
print("日期发生变化: \(String(describing: a))")
}
}
deinit {
self.myObject.removeObserver(self, forKeyPath: "date")
}
}
我们标明了 MyClass
的 date
为 dynamic
,然后在一个 Class
的 init
中将自己添加为该实例的观察者。接下来等待了三秒钟之后改变了这个对象的被观察属性,这时我们的观察方法就将被调用。运行这段代码,输出应该类似于:
初始化 MyClass, 当前日期为:2017-08-24 06:55:40 +0000
日期发生变化: Optional(2017-08-24 06:55:44 +0000)
别忘了,新的值是从字典中取出的。虽然我们能够确定(其实是Cocoa向我们保证)这个字典中会有相应的键值,但是在实际使用的时候我们最好还是进行一下判断或者Optional Binding后再加 以使用,毕竟世事难料。
在Swift中使用KVO有两个显而易见的问题。
首先是Swift的KVO需要依赖的东西比原来多。在Objective-C中我们几乎可以没有限制地对所有满足KVC的属性进行监听,而现在我们需要属性有 dynamic
进行修饰。大多数情况下,我们想要观察的类不一定是 dynamic
修饰的(除非这个类的开发者有意为之,否则一般也不会有人愿意多花功夫在属性前加上dynamic
,因为这毕竟要损失一部分性能),并且有时候我们很可能也无法修改想要观察的类的源码。遇到这样的情况的话,一个可能可行的方案是继承这个类并且将需要观察的属性使用 dynamic
进行重写。比如刚才我们的 MyClass
中如果 date
没有 dynamic
的话,我们可能就需要一个新的 MyChildClass
了:
//当无法修改想要观察的源码时,可以继承这个类并且将需要观察的属性使用dynamic重写。
class MyClass1: NSObject {
var date = Date()
}
class MyChildClass: MyClass {
dynamic override var date: Date {
get { return super.date as Date }
set { super.date = newValue }
}
}
private var myContext = 0
class Class1: NSObject{
var myObj:MyChildClass!
override init() {
super.init()
myObj = MyChildClass()
print("初始化MyChildClass,当前时间为: \(myObj.date)")
myObj.addObserver(self, forKeyPath: "date", options:.new, context: &myContext)
delay(3) {
self.myObj.date = Date()
}
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let change = change, context == &myContext {
let newDate = change[NSKeyValueChangeKey.newKey]
print("日期发生变化:\(newDate)")
}
}
deinit {
self.myObj.removeObserver(self, forKeyPath: "date")
}
}
//对于非NSObject子类可以通过属性观察来实现监听功能。
对于这种重载,我们没有必要改变什么逻辑,所以在子类中简单地用 super
去调用父类里相关的属性就可以了。
另一个大问题是对于那些非的Swift类型怎么办。因为Swift类型并没有通过KVC进行实现,所以更不用谈什么对属性进行KVO了。对于Swift类型,语言中现在暂时还没有原生的类 似KVO的观察机制。我们可能只能通过属性观察来实现一套自己的类似替代了。结合泛型和闭包这些Swift的先进特性(当然是相对于Objective-C来说的先进特性),把API做得比原来的KVO更优雅其实不是一件难事。Observable-Swift 就利用了这个思路实现了一套对Swift类型进行观察的机制,如果您也有类似的需求,不妨可以参考看看。