Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift 4 approach for observeValue(forKeyPath:...)

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.

like image 991
MVZ Avatar asked Nov 29 '17 22:11

MVZ


People also ask

Can you explain KVO?

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.

What is KVO ios?

Key-value observing is a Cocoa programming pattern you use to notify objects about changes to properties of other objects. It's useful for communicating changes between logically separated parts of your app—such as between models and views.

What framework is KVO key-value observing a part of?

Key-Value Observing, KVO for short, is an important concept of the Cocoa API. It allows objects to be notified when the state of another object changes.

What is key-value observing?

Key-value observing is a mechanism that enables an object to be notified directly when a property of another object changes. It is a mode of communication between objects in applications designed in conformance with the Model-View-Controller design pattern.


2 Answers

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:

self.observation = object.observe(\.keyPath) { 
    [unowned self] object, change in
    self.someFunction()
}

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 where Type is a concrete type name (incl. any generic parameters), and keyPath 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:

\SomeStruct.someValue
\.someClassProperty
\.someInstance.someInnerProperty
\[Int].[1]
\[String].first?.count
\[SomeHashable: [Int]].["aStringLiteral, literally"]!.count.bitWidth

Ownership

You're the owner of the NSKeyValueObservation instance the observe function returns, meaning, you don't have to addObserver nor removeObserver 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'll deinit gracefully. So, you can let it live until the instance holding it dies, stop it manually by niling the reference, or even invoke invalidate() 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 declared dynamic.

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 call value(forKey(Path):)

Beyond KVO

There's much more to Key-Path Expressions than KVO. \Type.path expressions can be stored as KeyPath 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.

like image 92
Matias Pequeno Avatar answered Oct 20 '22 23:10

Matias Pequeno


To add something to the answer as I experienced crashes on my app when using this method in iOS 10.

In iOS 10, you still need to remove the observer before deallocating the class or otherwise you will get a crash NSInternalInconsistencyException stating that:

An instance A of Class C was deallocated while key value observers were still registered with it.

To avoid this crash. Simply set the observer property that you're using to nil.

deinit {
    self.observation = nil
}    
like image 23
barbarity Avatar answered Oct 21 '22 00:10

barbarity