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")
    }

}

我们标明了 MyClassdatedynamic,然后在一个 Classinit 中将自己添加为该实例的观察者。接下来等待了三秒钟之后改变了这个对象的被观察属性,这时我们的观察方法就将被调用。运行这段代码,输出应该类似于:

初始化 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类型进行观察的机制,如果您也有类似的需求,不妨可以参考看看。

results matching ""

    No results matching ""