Skip to content

Instantly share code, notes, and snippets.

@correia
Last active April 16, 2023 02:38
Show Gist options
  • Save correia/001923bc420b942f9865 to your computer and use it in GitHub Desktop.
Save correia/001923bc420b942f9865 to your computer and use it in GitHub Desktop.
A quick example for how to use Foundation style KVO from Swift. (Official documentation from Apple is forthcoming.)
//
// Swift-KVO
//
// Created by Jim Correia on 6/5/14.
// Copyright (c) 2014-2015 Jim Correia. All rights reserved.
//
// Update: 6/17/2014
//
// KVOContext has gone away; use the same idiom you'd use from Objective-C for the context
//
// Update 6/10/2015
//
// Updated for Swift 2
// Use dynamic on observable properties
import Foundation
typealias KVOContext = UInt8
var MyObservationContext = KVOContext()
class Observer: NSObject {
func startObservingPerson(person: Person) {
let options = NSKeyValueObservingOptions([.New, .Old])
person.addObserver(self, forKeyPath: "firstName", options: options, context: &MyObservationContext)
person.addObserver(self, forKeyPath: "lastName", options: options, context: &MyObservationContext)
}
func stopObservingPerson(person: Person) {
person.removeObserver(self, forKeyPath: "firstName", context: &MyObservationContext)
person.removeObserver(self, forKeyPath: "lastName", context: &MyObservationContext)
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [NSObject : AnyObject]?, context: UnsafeMutablePointer<Void>) {
guard keyPath != nil else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
return
}
switch (keyPath!, context) {
case("firstName", &MyObservationContext):
print("First name changed: \(change)")
case("lastName", &MyObservationContext):
print("Last name changed: \(change)")
case(_, &MyObservationContext):
assert(false, "unknown key path")
default:
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
}
class Person: NSObject {
dynamic var firstName: String?
dynamic var lastName: String?
}
let person = Person()
let observer = Observer()
observer.startObservingPerson(person)
person.firstName = "Jim"
person.lastName = "Correia"
observer.stopObservingPerson(person)
@alonecuzzo
Copy link

👍

@cmkilger
Copy link

Does this work for you? It crashes for me.

fatal error: Can't unwrap Optional.None

Removing the options and using just .New instead gives me this:

Playground execution failed: error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=1, address=0x0).
The process has been left at the point where it was interrupted, use "thread return -x" to return to the state before expression evaluation.
* thread #1: tid = 0x217a75, 0x00000001009362e0 libobjc.A.dylib`_class_getNonMetaClass + 155, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
  * frame #0: 0x00000001009362e0 libobjc.A.dylib`_class_getNonMetaClass + 155
    frame #1: 0x0000000100931366 libobjc.A.dylib`_class_resolveMethod + 100
    frame #2: 0x000000010093a92b libobjc.A.dylib`lookUpImpOrForward + 357
    frame #3: 0x000000010093a738 libobjc.A.dylib`class_getInstanceMethod + 52
    frame #4: 0x0000000100396b9f Foundation`+[NSObject(NSKeyValueObservingCustomization) keyPathsForValuesAffectingValueForKey:] + 212
    frame #5: 0x000000010039681f Foundation`-[NSKeyValueUnnestedProperty _givenPropertiesBeingInitialized:getAffectingProperties:] + 149
    frame #6: 0x000000010039650d Foundation`-[NSKeyValueUnnestedProperty _initWithContainerClass:key:propertiesBeingInitialized:] + 151
    frame #7: 0x000000010039611e Foundation`NSKeyValuePropertyForIsaAndKeyPathInner + 288
    frame #8: 0x0000000100395dd8 Foundation`NSKeyValuePropertyForIsaAndKeyPath + 174
    frame #9: 0x00000001003b8991 Foundation`-[NSObject(NSKeyValueObserverRegistration) addObserver:forKeyPath:options:context:] + 79
    frame #10: 0x000000010bf012b3

@DarrenHurst
Copy link

extension Person {
override class func keyPathsForValuesAffectingValueForKey(key: String!) -> NSSet! {
return nil
}

override class func automaticallyNotifiesObserversForKey(key: String!) -> Bool {
    return true
}

}

@DarrenHurst
Copy link

fixes the missing NSObject methods

@jameswomack
Copy link

An example project that demonstrates KVO being used in a UIKit interface via Swift:
https://github.com/jameswomack/kvo-in-swift

@jshah
Copy link

jshah commented Apr 22, 2015

Do the variables in the Person class, firstName and lastName need the "dynamic" property on it?

@neilpa
Copy link

neilpa commented May 29, 2015

@jshah111 - Yea, dynamic is needed if you actually want to get KVO notifications.

@correia
Copy link
Author

correia commented Jun 10, 2015

Updated for Swift 2 on 2015/06/10

@Gouldsc
Copy link

Gouldsc commented Jun 22, 2015

EDIT: This is working in Xcode 7 Beta 2. Disregard previous statement. 7/30/15

@andrewschreiber
Copy link

On line 33

override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [NSObject :        AnyObject]?, context: UnsafeMutablePointer<Void>) {
        guard keyPath != nil else {
            super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
            return
        }

I needed to replace

[NSObject: AnyObject]?

with

[String:AnyObject]?

@colinta
Copy link

colinta commented Feb 8, 2016

guard let keyPath = keyPath else { ... }

switch keyPath { ... }
--- yours
+++ patched
@@ -31,12 +31,12 @@
     }

     override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [NSObject : AnyObject]?, context: UnsafeMutablePointer<Void>) {
-        guard let keyPath = keyPath else {
+        guard keyPath != nil else {
             super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
             return
         }

-        switch (keyPath, context) {
+        switch (keyPath!, context) {
         case("firstName", &MyObservationContext):
             print("First name changed: \(change)")

@ltfschoen
Copy link

This is brilliant. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment