I've been trying to find an example, but what I've seen doesn't work in my case.
What would be the equivalent of the following code:
object.addObserver(self, forKeyPath: "keyPath", options: [.new], context: nil)
override public func observeValue(
forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?) {
}
The code above works, but I get a warning from SwiftLink:
Prefer the new block based KVO API with keypaths when using Swift 3.2 or later.
I appreciate it if you can point me in the right direction.
Swift 4 introduced a family of concrete Key-Path types, a new Key-Path Expression to produce them and a new closure-based observe function available to classes that inherit
NSObject
.Using this new set of features, your particular example can now be expressed much more succinctly:
Types Involved
observation:
NSKeyValueObservation
change:
NSKeyValueObservedChange
\.keyPath
: An instance of a KeyPath class produced at compile time.Key-Path grammar
The general grammar of a Key-Path Expression follows the form
\Type.keyPath
whereType
is a concrete type name (incl. any generic parameters), andkeyPath
a chain of one or more properties, subscripts, or optional chaining/forced unwrapping postfixes. In addition, if the keyPath's Type can be inferred from context, it can be elided, resulting in a most pithy\.keyPath
.These are all valid Key-Path Expressions:
Ownership
You're the owner of the
NSKeyValueObservation
instance theobserve
function returns, meaning, you don't have toaddObserver
norremoveObserver
anymore; rather, you keep a strong reference to it for as long as you need your observation observing.You're not required to
invalidate()
either: it'lldeinit
gracefully. So, you can let it live until the instance holding it dies, stop it manually bynil
ing the reference, or even invokeinvalidate()
if you need to keep your instance alive for some smelly reason.Caveats
As you may have noticed, observation still lurks inside the confines of Cocoa's KVO mechanism, therefore it's only available to Obj-C classes and Swift classes inheriting
NSObject
(every Swift-dev's favorite type) with the added requirement that any value you intend to observe, must be marked as@objc
(every Swift-dev's favorite attribute) and declareddynamic
.That being said, the overall mechanism is a welcomed improvement, particularly because it manages to Swiftify observing imported
NSObjects
from modules we may happen to be required to use (eg.Foundation
), and without risking weakening the expressive power we work so hard to obtain with every keystroke.As a side-note, Key-Path String Expressions are still required to dynamically access
NSObject
's properties to KVC or callvalue(forKey(Path):)
Beyond KVO
There's much more to Key-Path Expressions than KVO.
\Type.path
expressions can be stored asKeyPath
objects for later reuse. They come in writable, partial and type-erased flavors. They can augment the expressive power of getter/setter functions designed for composition, not to mention the role they play in allowing those with the strongest of stomachs to delve into the world of functional concepts like Lenses and Prisms. I suggest you check the links down below to learn more about the many development doors they can open.Links:
Key-Path Expression @ docs.swift.org
KVO docs @ Apple
Swift Evolution Smart KeyPaths proposal
Ole Begemann's Whats-new-in-Swift-4 playground with Key-Path examples
WWDC 2017 Video: What's New in Foundation 4:35 for SKP and 19:40 for KVO.