Swift 4 前后 KVO 的变化

如果了解过设计模式的同学,应该都知道有一种设计模式叫做观察者模式,属于行为型模式,即当对象存在一对多的依赖关系,当一个对象发生变化时,需要自动通知它的依赖对象。通常用于实时事件处理。

我们来研究一下 iOS 里对观察者模式的支持,即 KVO(key-value observing) ,键值对观察,其原理是基于 KVC(key-value-coding)runtime。通过 Swfit 研究。

KVO 使用

由于 Swift4 之后 KVOapi 有所改变,所以先来看看 Swift4 之前使用 KVO

class Test: NSObject {
    dynamic var field = "field"
}
复制代码
var test = Test()
override func viewDidLoad() {
    super.viewDidLoad()
    test.addObserver(self, forKeyPath: "field", options: [.new, .initial], context: nil)
    test.field = "change"
}

override func observeValue(forKeyPath keyPath: String?,
                           of object: Any?,
                           change: [NSKeyValueChangeKey : Any]?,
                           context: UnsafeMutableRawPointer?) {
    print(change)
}
deinit {
    test.removeObserver(self, forKeyPath: "field")
}
复制代码

在Swift4之前使用 KVO ,即需要在 deinit 中调用 removeObserver ,否则会crash,还需要重写 NSObject

func observeValue(forKeyPath keyPath: String?, 
                  of object: Any?, 
                  change: [NSKeyValueChangeKey : Any]?, 
                  context: UnsafeMutableRawPointer?)
复制代码

以完成更多的操作。

而在Swift4中,KVO的 API 变得友好很多。

@objcMembers class Test: NSObject {
    dynamic var field = "field"
}

var observer: NSKeyValueObservation!
    override func viewDidLoad() {
        super.viewDidLoad()
        let test = Test()
        observer = test.observe(\.field, options: [.new, .initial]) { (object, change) in
            print(change)
        }
        test.field = "change"
    }
复制代码

通过闭包优化了 KVO 的实现,在官方WWDC视频上What's New in Foundation专题上介绍 KVO 时,表示该函数会返回一个 NSKeyValueObservation,所以只需要管理这个实例的生命周期,不再需要移除观察者,再也不用担心忘记移除观察者而导致crash。(ps: 可以尝试在方法内声明 NSKeyValueObservation 对象,可以发现即使改变了属性值也不会调用闭包内的操作。 因为随着方法的结束,这个实例和闭包的生命周期都结束了。)

在闭包中第一个参数是对被观察者的引用,防止在闭包内使用被观察者而导致循环引用的问题(相当nice)。

遗憾的是只有继承于 NSObject 的对象才能够使用 KVO

KVO 原理

Swift 4 之前

采用断点调试

addObserver 之前

addObserver 之后

可见,在 addObserver 之后, test 实例会将isa指针指向 NSKVONotifying_Test 的派生类

再通过runtime来具体看看这个两个类的区别

采用辅助函数,查看 test 类和方法的变化。

func find() {
    print(NSStringFromClass(object_getClass(test)!))
    print(String(describing: class_getName(object_getClass(test))))
    var count: UInt32 = 0
    let methodlist = class_copyMethodList(object_getClass(test), &count)
    for i in 0..<count {
        print(NSStringFromSelector(method_getName(methodlist![Int(i)])))
    }
    print("\n")
}
复制代码

addObserver之前

addObserver之后

从上图输出结果可见,在 addObserver 之后类发生了改变,并且添加了一个私有属性 _isKVOA,从名字可以推测是用于对类标示,以此来标示是 KVO

从图中可以看出 NSKVONotifying_Test 重写了被观察属性 fieldset 方法(即 setField: )。再来看看具体是怎么重写的。

根据 KVOapi 有手动调用的方法。

func willChangeValue(forKey key: String)
func didChangeValue(forKey key: String)
复制代码

可以推测是在 set 方法内添加 func willChangeValue(forKey key: String)func didChangeValue(forKey key: String)

通过堆栈信息具体看一看调用情况。

第一次调用时 initial ,第二次是属性发生变化时调用的。从堆栈信息一目了然。

Swift 4 之后

接下来我们来探讨下,Swift4 之后 KVO的新 API ,具体的底层原理。 先根据上面的方法测试下是否是通过 runtime 新增 NSKVONotifying_Test 派生类实现的。

从测试结果可见,与 Swift4 之前的原理一致。但是区别在于 NSKVONotifying_Test 的生命周期由 NSKeyValueObservation 管理,通过断点调试看看 NSKeyValueObservation

从上图可见, NSKeyValueObservation 内有一个 object 属性 是指向观察者 testcallback 回调闭包,以及 path 代指被观察的属性。

从堆栈信息,可以一目了然的看到当被观察属性发生改变时,调用情况。 NSKeyValueObservation 作为了观察者和消息转发者,接收通知和通知 test 的属性发生改变,从而调用 闭包 内的具体操作。

才疏学浅,如有什么理解不到位的欢迎指出。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值