Skip to content

Instantly share code, notes, and snippets.

@jzucker2
Last active February 9, 2017 17:24
Show Gist options
  • Save jzucker2/6098e8e705b1732e6b6d22d502f15167 to your computer and use it in GitHub Desktop.
Save jzucker2/6098e8e705b1732e6b6d22d502f15167 to your computer and use it in GitHub Desktop.
Proper KVO in Swift
import Foundation
class Foo: NSObject {
dynamic var bar: Int = 0
private let bazKeyPath = #keyPath(Foo.baz)
private var _baz: Int = 0 {
set {
willChangeValue(forKey: bazKeyPath)
self._baz = newValue
didChangeValue(forKey: bazKeyPath)
}
get {
willAccessValue(forKey: bazKeyPath)
let returningBaz = self._baz
didAccessValue(forKey: bazKeyPath)
return returningBaz
}
}
// custom KVO variable
dynamic var baz: Int
}
import Foundation
class Observer: NSObject {
private var observerKVOContext = 0
// MARK: - Objects to observe
var foo: Foo? {
didSet {
let observingKeyPaths = [#keyPath(Foo.bar), #keyPath(Foo.baz)]
observingKeyPaths.forEach { (keyPath) in
oldValue?.removeObserver(self, forKeyPath: keyPath, context: &observerKVOContext)
self.foo?.addObserver(self, forKeyPath: keyPath, options: [.new, .old, .initial], context: &observerKVOContext)
}
}
}
// MARK: - Deinit
deinit {
self.foo = nil
}
// MARK: - KVO
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if context == &observerKVOContext {
guard let existingKeyPath = keyPath else {
return
}
switch existingKeyPath {
case #keyPath(Foo.bar):
// Do KVO based update here
case #keyPath(Foo.baz):
// Do other KVO stuff here
default:
fatalError("We did not implement this keyPath (\(existingKeyPath)) so how did we end up here?")
}
} else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
}
import UIKit
class ObservingViewController: UIViewController {
private var observerVCKVOContext = 0
var currentFoo: User? {
didSet {
let observingKeyPaths = [#keyPath(Foo.bar)]
observingKeyPaths.forEach { (keyPath) in
oldValue?.removeObserver(self, forKeyPath: keyPath, context: &observerVCKVOContext)
self.currentFoo?.addObserver(self, forKeyPath: keyPath, options: [.new, .old, .initial], context: &observerVCKVOContext)
}
}
}
deinit {
currentFoo = nil
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
currentFoo = Foo.sharedFoo.currentFoo // get foo from wherever, this will update UI
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
// make sure to remove currentFoo to remove
// listeners (can optionally just add/remove
// listeners instead), but it's important that
// there be no KVO updates when the view is off
// screen, in case anything goes out of scope
// and is deallocated (deinit is usually too late)
currentFoo = nil
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if context == &observerVCKVOContext {
print("KVO: \(keyPath)")
guard let existingKeyPath = keyPath else {
return
}
switch existingKeyPath {
case #keyPath(Foo.bar):
// Do KVO based update here
case #keyPath(Foo.baz):
// Do other KVO stuff here
default:
fatalError("We did not implement this keyPath (\(existingKeyPath)) so how did we end up here?")
}
} else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment