Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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

This comment has been minimized.

Copy link

commented Jun 10, 2014

👍

@cmkilger

This comment has been minimized.

Copy link

commented Jun 20, 2014

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

This comment has been minimized.

Copy link

commented Jul 28, 2014

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

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

}

@DarrenHurst

This comment has been minimized.

Copy link

commented Jul 28, 2014

fixes the missing NSObject methods

@jameswomack

This comment has been minimized.

Copy link

commented Aug 4, 2014

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

@jshah

This comment has been minimized.

Copy link

commented Apr 22, 2015

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

@neilpa

This comment has been minimized.

Copy link

commented May 29, 2015

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

@correia

This comment has been minimized.

Copy link
Owner Author

commented Jun 10, 2015

Updated for Swift 2 on 2015/06/10

@Gouldsc

This comment has been minimized.

Copy link

commented Jun 22, 2015

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

@andrewschreiber

This comment has been minimized.

Copy link

commented Oct 18, 2015

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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

commented Apr 28, 2016

This is brilliant. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.