Swift 4 approach for observeValue(forKeyPath:…)

与世无争的帅哥 提交于 2020-02-26 04:50:55

问题


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.


回答1:


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.




回答2:


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
}    


来源:https://stackoverflow.com/questions/47562977/swift-4-approach-for-observevalueforkeypath

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!