KVO, which stands for Key-Value Observing, is one of the techniques for observing the program state changes available in Objective-C and Swift. The concept is simple: when we have an object with some instance variables, KVO allows other objects to establish surveillance on changes for any of those instance variables.
KVO and KVC or Key-Value Observing and Key-Value Coding are mechanisms originally built and provided by Objective-C that allows us to locate and interact with the underlying properties of a class that inherits NSObject at runtime.
Key-Value Pairs as a Function Parameter When calling a function with a KeyValuePairs parameter, you can pass a Swift dictionary literal without causing a Dictionary to be created. This capability can be especially important when the order of elements in the literal is significant.
According to Apple: Key-value coding is a mechanism for accessing an object's properties indirectly, using strings to identify properties, rather than through invocation of an accessor method or accessing them directly through instance variables.
You can use KVO in Swift, but only for dynamic
properties of NSObject
subclass. Consider that you wanted to observe the bar
property of a Foo
class. In Swift 4, specify bar
as dynamic
property in your NSObject
subclass:
class Foo: NSObject {
@objc dynamic var bar = 0
}
You can then register to observe changes to the bar
property. In Swift 4 and Swift 3.2, this has been greatly simplified, as outlined in Using Key-Value Observing in Swift:
class MyObject {
private var token: NSKeyValueObservation
var objectToObserve = Foo()
init() {
token = objectToObserve.observe(\.bar) { [weak self] object, change in // the `[weak self]` is to avoid strong reference cycle; obviously, if you don't reference `self` in the closure, then `[weak self]` is not needed
print("bar property is now \(object.bar)")
}
}
}
Note, in Swift 4, we now have strong typing of keypaths using the backslash character (the \.bar
is the keypath for the bar
property of the object being observed). Also, because it's using the completion closure pattern, we don't have to manually remove observers (when the token
falls out of scope, the observer is removed for us) nor do we have to worry about calling the super
implementation if the key doesn't match. The closure is called only when this particular observer is invoked. For more information, see WWDC 2017 video, What's New in Foundation.
In Swift 3, to observe this, it's a bit more complicated, but very similar to what one does in Objective-C. Namely, you would implement observeValue(forKeyPath keyPath:, of object:, change:, context:)
which (a) makes sure we're dealing with our context (and not something that our super
instance had registered to observe); and then (b) either handle it or pass it on to the super
implementation, as necessary. And make sure to remove yourself as an observer when appropriate. For example, you might remove the observer when it is deallocated:
In Swift 3:
class MyObject: NSObject {
private var observerContext = 0
var objectToObserve = Foo()
override init() {
super.init()
objectToObserve.addObserver(self, forKeyPath: #keyPath(Foo.bar), options: [.new, .old], context: &observerContext)
}
deinit {
objectToObserve.removeObserver(self, forKeyPath: #keyPath(Foo.bar), context: &observerContext)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard context == &observerContext else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}
// do something upon notification of the observed object
print("\(keyPath): \(change?[.newKey])")
}
}
Note, you can only observe properties that can be represented in Objective-C. Thus, you cannot observe generics, Swift struct
types, Swift enum
types, etc.
For a discussion of the Swift 2 implementation, see my original answer, below.
Using the dynamic
keyword to achieve KVO with NSObject
subclasses is described in the Key-Value Observing section of the Adopting Cocoa Design Conventions chapter of the Using Swift with Cocoa and Objective-C guide:
Key-value observing is a mechanism that allows objects to be notified of changes to specified properties of other objects. You can use key-value observing with a Swift class, as long as the class inherits from the
NSObject
class. You can use these three steps to implement key-value observing in Swift.
Add the
dynamic
modifier to any property you want to observe. For more information ondynamic
, see Requiring Dynamic Dispatch.class MyObjectToObserve: NSObject { dynamic var myDate = NSDate() func updateDate() { myDate = NSDate() } }
Create a global context variable.
private var myContext = 0
Add an observer for the key-path, and override the
observeValueForKeyPath:ofObject:change:context:
method, and remove the observer indeinit
.class MyObserver: NSObject { var objectToObserve = MyObjectToObserve() override init() { super.init() objectToObserve.addObserver(self, forKeyPath: "myDate", options: .New, context: &myContext) } override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) { if context == &myContext { if let newValue = change?[NSKeyValueChangeNewKey] { print("Date changed: \(newValue)") } } else { super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context) } } deinit { objectToObserve.removeObserver(self, forKeyPath: "myDate", context: &myContext) } }
[Note, this KVO discussion has subsequently been removed from the Using Swift with Cocoa and Objective-C guide, which has been adapted for Swift 3, but it still works as outlined at the top of this answer.]
It's worth noting that Swift has its own native property observer system, but that's for a class specifying its own code that will be performed upon observation of its own properties. KVO, on the other hand, is designed to register to observe changes to some dynamic property of some other class.
(Edited to add new info): consider whether using the Combine framework can help you accomplish what you wanted, rather than using KVO
Yes and no. KVO works on NSObject subclasses much as it always has. It does not work for classes that don't subclass NSObject. Swift does not (currently at least) have its own native observation system.
(See comments for how to expose other properties as ObjC so KVO works on them)
See the Apple Documentation for a full example.
Both yes and no:
Yes, you can use the same old KVO APIs in Swift to observe Objective-C objects.
You can also observe dynamic
properties of Swift objects inheriting from NSObject
.
But... No it's not strongly typed as you could expect Swift native observation system to be.
Using Swift with Cocoa and Objective-C | Key Value Observing
No, currently there is no builtin value observation system for arbitrary Swift objects.
Yes, there are builtin Property Observers, which are strongly typed.
But... No they are not KVO, since they allow only for observing of objects own properties, don't support nested observations ("key paths"), and you have to explicitly implement them.
The Swift Programming Language | Property Observers
Yes, you can implement explicit value observing, which will be strongly typed, and allow for adding multiple handlers from other objects, and even support nesting / "key paths".
But... No it will not be KVO since it will only work for properties which you implement as observable.
You can find a library for implementing such value observing here:
Observable-Swift - KVO for Swift - Value Observing and Events
An example might help a little here. If I have an instance model
of class Model
with attributes name
and state
I can observe those attributes with:
let options = NSKeyValueObservingOptions([.New, .Old, .Initial, .Prior])
model.addObserver(self, forKeyPath: "name", options: options, context: nil)
model.addObserver(self, forKeyPath: "state", options: options, context: nil)
Changes to these properties will trigger a call to:
override func observeValueForKeyPath(keyPath: String!,
ofObject object: AnyObject!,
change: NSDictionary!,
context: CMutableVoidPointer) {
println("CHANGE OBSERVED: \(change)")
}
Yes.
KVO requires dynamic dispatch, so you simply need to add the dynamic
modifier to a method, property, subscript, or initializer:
dynamic var foo = 0
The dynamic
modifier ensures that references to the declaration will be dynamically dispatched and accessed through objc_msgSend
.
In addition to Rob's answer. That class must inherit from NSObject
, and we have 3 ways to trigger property change
Use setValue(value: AnyObject?, forKey key: String)
from NSKeyValueCoding
class MyObjectToObserve: NSObject {
var myDate = NSDate()
func updateDate() {
setValue(NSDate(), forKey: "myDate")
}
}
Use willChangeValueForKey
and didChangeValueForKey
from NSKeyValueObserving
class MyObjectToObserve: NSObject {
var myDate = NSDate()
func updateDate() {
willChangeValueForKey("myDate")
myDate = NSDate()
didChangeValueForKey("myDate")
}
}
Use dynamic
. See Swift Type Compatibility
You can also use the dynamic modifier to require that access to members be dynamically dispatched through the Objective-C runtime if you’re using APIs like key–value observing that dynamically replace the implementation of a method.
class MyObjectToObserve: NSObject {
dynamic var myDate = NSDate()
func updateDate() {
myDate = NSDate()
}
}
And property getter and setter is called when used. You can verify when working with KVO. This is an example of computed property
class MyObjectToObserve: NSObject {
var backing: NSDate = NSDate()
dynamic var myDate: NSDate {
set {
print("setter is called")
backing = newValue
}
get {
print("getter is called")
return backing
}
}
}
Currently Swift does not support any built in mechanism for observing property changes of objects other than 'self', so no, it does not support KVO.
However, KVO is such a fundamental part of Objective-C and Cocoa that it seems quite likely that it will be added in the future. The current documentation seems to imply this:
Key-Value Observing
Information forthcoming.
Using Swift with Cocoa and Objective-C
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With