Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
Here is a playground demonstrating how to set up Swift 4 KVO.
//: # Setting up Swift 4 KVO
import Foundation
//: This class has a pair of properties that can be observied by KVO.
//: - Note: This class *must* inheret from `NSObject` in order to posess the KVO functionality.
class ObservableClass: NSObject {
/// An observable string property. Note that it most be annotated with both "@objc" (expose the property to the
/// Objective-C runtime) and "dynamic" (enables KVO for the property).
@objc dynamic private(set) var stringProperty: String = "Starting string!"
/// An observable Date property.
@objc dynamic private(set) var dateProperty: Date = Date()
/// A method to update the string property.
func updateString(to string: String) {
self.stringProperty = string
/// A method to update the date property.
func updateDate(secondsSinceNow: TimeInterval) {
self.dateProperty = Date(timeIntervalSinceNow: secondsSinceNow)
//: This is the class which will be observing the changes and letting us know when it happens.
class ObservingClass {
/// Just a simple date formatter to print out the dates.
static let dateFormatter: DateFormatter = {
let df = DateFormatter()
df.timeStyle = .short
df.dateStyle = .medium
return df
/// We use this array to hold on to our KVO observations so that
var observations: [NSKeyValueObservation] = []
/// Initialize this class with the object we want to observe.
init(observing object: ObservableClass) {
//: Here's where we create the KVO observations. Be sure you retian the returned `NSKeyValueObservation`
//: object or it will immediately deallocate and the KVO won't occur.
//: - Note: There are 2 signatures for this method in auto-complete. The first one omits the `options:`
//: parameter due to a default value. However, the default value does not specify that any of
//: the values be supplied in the change object (they will all be `nil`). The KVO closure will
//: still be triggered, so you can tell that there was a change, but you'd have to manually
//: inspect the property to see what the new value was.
observations.append(object.observe(\.stringProperty, options: [.new], changeHandler: { (object, change) in
guard let nv = change.newValue else { return }
print("The string property changed to: \(nv)")
// Now we'll observe both the old and new values of the date property.
observations.append(object.observe(\.dateProperty, options: [.old, .new], changeHandler: { (object, change) in
guard let old = change.oldValue, let new = change.newValue else { return }
let df = ObservingClass.dateFormatter
print("The date changed from \(df.string(from: old)) to \(df.string(from: new)).")
func killObservations() {
observations = []
//: Instantiate our observable and observer classes.
let observable = ObservableClass()
let observing = ObservingClass(observing: observable)
//: Change the observed properties and see the observations trigger.
observable.updateString(to: "Oh! The huge manatee!")
observable.updateDate(secondsSinceNow: 3600)
//: Release the observation objects.
//: Note that the observation does not trigger when we change the date again.
observable.updateDate(secondsSinceNow: 7200)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment