Swift 进阶笔记-业务化 Tips(4)-键路径 KeyPa
KeyPath
Swift 4.0 后添加了类型安全的 KeyPath 语法,KeyPath 是一个能对类型指向属性但未调用的引用的非常酷的特性。并且能让 struct 也支持 KVC。
struct User {
let name: String
var age: Int
}
let nameKeyPath = \User.name
let batman = User(name: "Bruce Wayne", age: 1)
batman[keyPath: nameKeyPath] // "Bruce Wayne"
KVO
KVO 是 iOS 开发中非常常见的一种观察者模式,这里不再赘述基本用法以及原理。但在使用 Swift 开发中,却没有OC中使用地那么频繁。在今年六月的 WWDC,Swift 为 KVO 做了一些 API 上的调整,使得 KVO 变得更加好用。
let observation = batman.observe(\.name, options: .new) { _, change in
...
}
/// 销毁
observation.invalidate()
在 Swift 4.0 以前,KeyPath 需要开发者手动设定字符串。如今使用 KeyPath 也让 KVO 变得更加简单安全更加优雅可读,但依然只有 NSObject 才能支持 KVO。
利用绑定的数据异步更新
首先为了使用 KVO,必须让涉及到的数据用 @objc dynamic 标示。
import Foundation
class User: NSObject {
// KVO-enabled properties must be @objc dynamic
@objc dynamic var name: String
init(name: String, age: Int) {
self.name = name
}
}
extension User {
override var description: String {
return "name: \(name)"
}
}
这里为了方便展示,自定义了 description,Swift 中类默认继承了 CustomStringConvertible,重写父类的 description 即可;结构体则需要引入 CustomStringConvertible 协议才能自定义 description。
接下来处理 KVO,将其封装为输入输出的形式。
/// 这段截取自 《Swift 进阶》
extension NSObjectProtocol where Self: NSObject {
func observe<A, Other>(_ keyPath: KeyPath<Self, A>, writeTo other: Other, _ otherKeyPath: ReferenceWritableKeyPath<Other, A>)
-> NSKeyValueObservation where A: Equatable, Other: NSObjectProtocol
{
return observe(keyPath, options: .new) { _, change in
guard let newValue = change.newValue, other[keyPath: otherKeyPath] != newValue else {
return
}
other[keyPath: otherKeyPath] = newValue
}
}
}
这里输入两个 KeyPath,为前者添加 KVO,更新内容后后者也同样更新。
和书中不同的是,我们只需要从数据更新到 UI,所以不使用双向绑定,双向绑定输入两个变量 KeyPath,返回两个 observation,有兴趣的可以根据上段方法自己实现,这里未使用就不放出来了)
接着实现我们的 ViewController:
import UIKit
class ViewController: UIViewController {
var user = User(name: "Gua Li")
/// Just Display current user props
@IBOutlet weak var displayLabel: UILabel!
var observation: NSKeyValueObservation?
override func viewDidLoad() {
super.viewDidLoad()
// 绑定
observation = user.observe(\User.name, writeTo: displayLabel, \UILabel.text!)
// 请求最新数据
getUserName { [weak self] in self?.user.name = $0 }
}
deinit {
// 解绑
observation?.invalidate()
}
/// 假装自己是个网络请求
func getUserName(completionHandler: ((String) -> Void)?) {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
completionHandler?("Peter Parker")
}
}
}
viewDidLoad中 进行绑定,并做一个网络请求(假)(捂脸)来获取用户 name。
效果展示
KVO.gif最终网络请求完成以后(2秒后)
getUserName { [weak self] in self?.user.name = $0 }
可以看到,仅仅改变了User的name值,displayLabel就已经更新。当然实际项目中一般不用使用 KVO 去更新网络数据,只是为了简单描述现在 KVO 是多么的简单易用。
Demo:
https://github.com/wiiale/AdvancedSwiftThinker/tree/master/T04-KeyPath-KVO
KeyPath 的作用远远不止那么局限,它的潜力有待发掘!
本册文集中以“提出简单需求->简单实现需求片段”为流程,内容只针对该知识点实现的业务实例进行熟悉,业务也必定存在比文章方法更好的实现方式,文章旨在分析知识点并加深理解。文集不普及基本知识,不包含《Swift 进阶》书籍的详细内容。深入学习Swift请大家支持正版书籍(ObjC 中国)