使用Swift中的闭包实现KVO

2019-02-14

用Swift闭包实现KVO

上一篇中已经提到,KVO是OC的运行时特性,Swift不具有的,要想使用这特性,必须要继承NSObject,但是对于Swift中的结构体与枚举类型就不能使用。现在有另一种方法不需要NSObject的子类,便可以实现KVO

使用别名typealias定义一个函数类型,回调内容变化

1
typealias KVObserveChange = (_ kvo: KeyValueObserver, _ change: [NSObject : AnyObject]) -> Void

然后,创建一个类KeyValueObserver保持着被观察者,键路径,观察者,内容变化回调函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class KeyValueObserver {
let source: NSObject// 被观察者
let keyPath: String // 键路径
private let observer: KVObserveChange // 观察的变化内容
private var defaultKVODispatcher = KVODispatcher()// 观察者
init(source: NSObject, keyPath: String, options: NSKeyValueObservingOptions, observer: @escaping KVObserveChange) {
self.source = source
self.keyPath = keyPath
self.observer = observer
source.addObserver(defaultKVODispatcher, forKeyPath: keyPath, options: options, context: self.pointer)
}

func __conversion() -> UnsafeMutablePointer<KeyValueObserver> {
return pointer
}
// 获取当前实例的指针
private lazy var pointer: UnsafeMutablePointer<KeyValueObserver> = {
return UnsafeMutablePointer<KeyValueObserver>(Unmanaged<KeyValueObserver>.passUnretained(self).toOpaque().assumingMemoryBound(to: KeyValueObserver.self))
}()
// 从pointer中获得值
private class func fromPointer(pointer: UnsafeMutablePointer<KeyValueObserver>) -> KeyValueObserver {
return Unmanaged<KeyValueObserver>.fromOpaque(UnsafeRawPointer(pointer)).takeUnretainedValue()
}

static func observe(pointer: UnsafeMutablePointer<KeyValueObserver>, change: [NSObject : AnyObject]) {
let kvo = fromPointer(pointer: pointer)
kvo.observer(kvo, change)
}

deinit {
source.removeObserver(defaultKVODispatcher, forKeyPath: keyPath, context: self.pointer)
print("deinit...\(self.pointer)")
}
}

class KVODispatcher : NSObject {
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
KeyValueObserver.observe(pointer: (UnsafeMutablePointer<KeyValueObserver>.init(mutating: context?.bindMemory(to: KeyValueObserver.self, capacity: MemoryLayout<KeyValueObserver>.stride)))!, change: change! as [NSObject : AnyObject])
}
}

使用

1
2
3
4
5
6
7
8
9
let button = UIButton()
var kvo: KeyValueObserver? = KeyValueObserver(source: button, keyPath: "selected", options: .new) {
(kvo, change) in
print("OBSERVE 2", kvo.keyPath, change)
}
button.isSelected = true
button.isSelected = false
kvo = nil
button.isSelected = true

如果不需要观察了,需要把观察者移出。同理,如果KeyValueOberser如果不是class,是struct也是可以的,只不过,struct时,代码需要做调整。因为struct是值类型,修改某个值时,通过函数前面需要加mutating,还有就是pointer属性返回的指针操作也需要通过bindMemory函数。可以调用下面这个函数

1
2
3
4
5
mutating func headPointerOfStruct() -> UnsafeMutablePointer<KeyValueObserver> {
return withUnsafeMutablePointer(to: &self) {
return UnsafeMutableRawPointer($0).bindMemory(to: KeyValueObserver.self, capacity: MemoryLayout<Self>.stride)
}
}

其他,KVODispatcher稍做调整即可实现功能。

KVO With Swift Closures