Last active
February 9, 2017 17:24
-
-
Save jzucker2/6098e8e705b1732e6b6d22d502f15167 to your computer and use it in GitHub Desktop.
Proper KVO in Swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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