Is key-value observation (KVO) available in Swift?
Solution 1
(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.
Solution 2
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.
Solution 3
Both yes and no:
Yes, you can use the same old KVO APIs in Swift to observe Objective-C objects.
You can also observedynamic
properties of Swift objects inheriting fromNSObject
.
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 ObservingNo, 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 ObserversYes, 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
Solution 4
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)")
}
Solution 5
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
.
Related videos on Youtube
codeperson
Updated on December 09, 2020Comments
-
codeperson over 3 years
If so, are there any key differences that weren't otherwise present when using key-value observation in Objective-C?
-
james_womack almost 10 yearsAn example project that demonstrates KVO being used in a UIKit interface via Swift: github.com/jameswomack/kvo-in-swift
-
Rob almost 10 years@JanDvorak See the KVO Programming Guide, which is a nice introduction to the topic.
-
Vincent over 8 yearsAlthough not an answer to your question, you can also start actions using the didset() function.
-
mfaani almost 6 yearsNote there is a Swift4 bug when you use
.initial
. For a solution see here. I highly recommend to see Apple docs. It's been updated recently and covers lots of important notes. Also see Rob's other answer
-
-
drewag about 10 yearsFYI, I filed a bug report with Apple about this. I encourage everyone else who would like to see a KVO or similar observation system in swift, to do the same.
-
fabb almost 10 yearsSince Xcode 6 beta 5 you can use the
dynamic
keyword on any Swift class to enable KVO support. -
Jerry almost 10 yearsHooray for @fabb! For clarity, the
dynamic
keyword goes on the property that you want to make key-value-observable. -
devth almost 10 yearsWhat is the purpose of
myContext
and how do you observe multiple properties? -
Rob almost 10 yearsAccording to KVO Programming Guide: "When you register an object as an observer, you can also provide a
context
pointer. Thecontext
pointer is provided to the observer whenobserveValueForKeyPath:ofObject:change:context:
is invoked. Thecontext
pointer can be a C pointer or an object reference. Thecontext
pointer can be used as a unique identifier to determine the change that is being observed, or to provide some other data to the observer." -
Rob almost 10 yearsObviously, that guide you reference now describes how to do KVO in Swift.
-
Max MacLeod almost 10 yearsYep, now implemented as of September 2014
-
Imanou Petit almost 10 yearsThe explanation for the
dynamic
keyword can be find in the Apple Developer Library's Using Swift with Cocoa and Objective-C section. -
Tim Arnold over 9 yearsSince this wasn't clear to me from @fabb's comment: use the
dynamic
keyword for any properties inside of a class you'd like to be KVO compliant (not thedynamic
keyword on the class itself). This worked for me! -
LiangWang about 9 yearsyou need to remove observer in deinit
-
Rob about 9 years@Jacky Agreed. That's where I generally remove the observer. I had simply quoted the Apple documentation, which didn't cover this key point, but it looks like they've finally updated it to reflect this. So I've updated my quote from their documentation accordingly.
-
Zmey almost 9 years@devth, as I understand, if subclass or superclass also registers KVO observer for the same variable, observeValueForKeyPath will be called multiple times. Context can be used to distinguish own notifications in this situation. More on this: dribin.org/dave/blog/archives/2008/09/24/proper_kvo_usage
-
hcanfly almost 9 yearsIn Xcode beta 6 it requires: override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>)
-
Leo Dabus over 8 yearsneed to change: [NSObject : AnyObject]? to [String : AnyObject]?
-
Rob over 8 years@LeoDabus - Agreed. The API has changed. Answer updated. Thanks.
-
Leo Dabus over 8 yearsIf I mark it as private it can't be accessed in MyObserver class
-
Rob over 8 years@LeoDabus - If this private global is defined within the same
.swift
file as theMyObserver
class, it can be accessed fine. And it seems better to do that and keep itprivate
, to avoid polluting the global namespace. -
mfaani about 8 yearsCan we say that
didSet
as KVO—to some extent? -
Catfish_Man about 8 yearsNot really; you can't register a new didSet from the "outside", it has to be part of that type at compile time.
-
Fattie over 7 yearsIf I'm not mistaken, the observeValueForKeyPath call approach is for Swift2.
-
Fattie over 7 yearsCan we KVO the alpha of a view? stackoverflow.com/q/41351175/294884
-
mfaani about 7 yearsI read here about the options, but still don't get what exactly do they mean in laymen's term. What would happen if you leave the options empty? What would happen if you remove
.old
? -
Rob about 7 yearsIf you leave
options
empty, it just means that thechange
won't include the old or new value (e.g. you might just get the new value yourself by referencing the object itself). If you just specify.new
and not.old
, it means thatchange
will include only the new value, but not the old value (e.g. you often don't care about what the old value was, but only care about the new value). If you needobserveValueForKeyPath
to pass you both the old and new value, then specify[.new, .old]
. Bottom line,options
just specifies what is included in thechange
dictionary. -
mfaani about 7 yearsThanks...and If I state
prior
then I will get 2 notifications? one for.old
and another for.new
? rather than getting a dictionary with 2 different keys? and if I stateinitial
ummm not sure really what happens!? -
Rob about 7 yearsRe
.prior
, yep, that's it. And re.initial
, generally when you set up notification, you don't receive a notification until the value changes. But you can use.initial
if you also want to receive a notification when you first set it up (e.g. if the initialization routine is identical to your change routine that you're already doing inobserveValue
, you can use.initial
to have it called immediately, too). It just depends upon what you're doing in yourobserveValue
routine. -
Niraj Paul over 5 yearsadded +1 for nice answer
-
Corbell over 4 yearsIn Swift 5.1 it appears that you can't get KVO support by simply adding
dynamic
to the property declaration; you must specify@objc dynamic
. Omitting the@objc
will result in aNSUnknownKeyException
when you callvalue(forKey:)
. -
J A S K I E R almost 3 yearsiOS 13 +? Other people were just ignored?