Swift 4.0 帶來的一個新功能就是 Smart KeyPath,之前在 Twitter 上看到 Chris Eidhof 大神在征集 KeyPath 的用法收壕。
我也搜集了一下额湘,當作是一次總結(jié)谴垫,這里面的技巧其實大部分都很難在實踐中用上蜓耻,只是好玩有趣而已蜡镶,也算是一種啟發(fā)吧雾袱。
類型安全的 Query API
出處:Kuery
利用了 KeyPath 類型安全的特性,提供了類型安全的 Query API官还。目前唯一做出來的一個成品是 Kuery 庫芹橡,類型安全的 CoreData 查詢 API,相同的方式也可以為 Realm望伦,SQLite 等數(shù)據(jù)庫服務林说,下面是它的使用范例:
Query(Person.self).filter(\.name != "Katsumi")
Query(Person.self).filter(\.age > 20)
其實我個人覺得這個 API 還可以再簡化:
Query.filter(\Person.name != "Katsumi")
// 或
Query<Person>.filter(\.name != "Katsumi")
這個庫的原理是操作符重載,大家看一下函數(shù)聲明就能大概理解了:
public func == <ManagedObject: NSManagedObject, Property: Equatable>(
lhs: KeyPath<ManagedObject, Property?>,
rhs: Property?)
-> NSPredicate<ManagedObject> { ... }
具體實現(xiàn)的時候使用了 KeyPath
的屬性 _kvcKeyPathString
屯伞,這是為了兼容 ObjectiveC 的 KVC 而存在的屬性腿箩,它并非是一個公開的 API,在正式文檔或 Xcode 里是查不到這個屬性的劣摇,具體的細節(jié)我們可以在 GitHub 上看到珠移。
雖然查不到,但目前代碼里是可以使用這個屬性的(Xcode 9.0末融,Swift 4.0)钧惧,Kuery 的作者也去 Rader 里反饋了將這個 API 正式化的需求,不過暫時還是不推薦大家使用這種方式勾习。
ReadOnly 的 Class
出處:Chris Eidhof
final class ReadOnly<T> {
private let value: T
init(_ value: T) {
self.value = value
}
subscript<P>(keyPath: KeyPath<T, P>) -> P {
return value[keyPath: keyPath]
}
}
import UIKit
let textField = UITextField()
let readOnlyTextField = ReadOnly(textFiled)
r[\.text] // nil
r[\.text] = "Test" // 編譯錯誤
這是個很好玩的實現(xiàn)垢乙,正常來說我們實現(xiàn)只讀,都是使用接口的權(quán)限設計语卤,例如 private(set)
之類的做法追逮,但這里利用了 KeyPath
無法修改值的特性實現(xiàn)了這一個功能酪刀,強行修改就會像上面那樣在編譯時就拋出錯誤。
不過這種只讀權(quán)限的顆粒度太大钮孵,只能細致到整個類實例骂倘,而不能針對每一個屬性。而且我在實踐中也沒有找到合適的使用場景巴席。
取代 Selector 的抽象
這是我在泊學網(wǎng)的會員群里偶然看到的历涝,11 說 Swift 4 里也有原生的 Selector。仔細想了一下漾唉,就只有 KeyPath 了荧库,實現(xiàn)出來大概會是這樣:
// 定義
extension UIControl {
func addTarget<T>(
_ target: T,
action: KeyPath<T, (UIControl) -> ()>,
for controlEvents: UIControlEvents)
{ ... }
}
// 調(diào)用
button.addTarget(self, action: \ViewController.didTapButton, for: .touchUpInside)
這樣處理的話,didTapButton
方法甚至都不需要依賴于 Objective-C 的 runtime赵刑,只要能用 KeyPath 把方法取出來就行了分衫。
但實際試了一下之后,發(fā)現(xiàn)并不可行般此,我就去翻了一下 KeyPath 的提案:
We think the disambiguating benefits of the escape-sigil would greatly benefit function type references, but such considerations are outside the scope of this proposal.
前半句其實我不太理解蚪战,但整句話讀下來,感覺應該是實現(xiàn)起來很復雜铐懊,會與另外的一個問題交織在一起邀桑,所以暫時不在這個提案里處理。我去翻郵件列表的時候終于找到了想要的答案:
for unapplied method references, bringing the two closely-related features into syntactic alignment over time and providing an opportunity to stage in the important but currently-source-breaking changes accepted in SE-0042 https://github.com/apple/swift-evolution/blob/master/proposals/0042-flatten-method-types.md.
KeyPath 指向方法的這個 Feature科乎,和 SE-0042 很接近壁畸,所以后面會兩個功能一起實現(xiàn)。
狀態(tài)共享的值類型
這應該算是這篇文章里面最 Tricky 但是也最有趣的一個用法了茅茂,我在看 Swift Talk 的時候捏萍,介紹的一種狀態(tài)共享的值類型,直接上代碼:
final class Var<A> {
private var _get: () -> A
private var _set: (A) -> ()
var value: A {
get { return _get() }
set { _set(newValue) }
}
init(_ value: A) {
var x = value
_get = { x }
_set = { x = $0 }
}
private init(get: @escaping () -> A, set: @escaping (A) -> ()) {
_get = get
_set = set
}
subscript<Part>(_ kp: WritableKeyPath<A, Part>) -> Var<Part> {
return Var<Part>(
get: { self.value[keyPath: kp] },
set: { self.value[keyPath: kp] = $0 })
}
}
看完代碼可能有點難理解玉吁,我們再看一下示例然后再解釋:
var john = Person(name: "John", age: 11)
let johnVar = Var(john)
let ageVar = johnVar[\.age]
print(johnVar.value.age) // 11
print(ageVar.value) // 11
ageVar.value = 22
print(johnVar.value.age) // 22
print(ageVar.value) // 22
johnVar.value.age = 33
print(johnVar.value.age) // 33
print(ageVar.value) // 33
上面我們可以看到 ageVar
從 johnVar
分割出來之后,它的狀態(tài)依舊跟 johnVar
保持一致腻异,這是因為 Var
的 init 方法里使用 block 捕獲了 x
這個變量进副,也就相當于作為 inout 參數(shù)傳入了進去,這個時候 x
會存放在堆區(qū)悔常。
并且使用 subscript 生成了 ageVar
之后影斑,ageVar
使用的 init 的方法只是在原本的 _get
和 _set
方法外面再包了一層,所以 ageVar
修改值的時候机打,也是使用了原本 johnVar
一樣的 _set
矫户,修改了最初 johnVar
初始化時使用的 x
。換句話說残邀,ageVar
和 johnVar
使用的都是堆區(qū)里同一個 x
皆辽。聽著是不是很像 class柑蛇???
更具體的細節(jié),大家可以去看 Swift Talk驱闷。
結(jié)尾
KeyPath is incredibly important in Cocoa Development. And this is they let us reason about the structure of our types apart from any specific instance in a way that's far more constrained than a closure.
—— What's New in Foundation · WWDC 2017 · Session 212
上面這段話摘錄自今年 WWDC 的 What's New in Foundation耻台,簡單的翻譯就是 KeyPath 對于 Cocoa 的使用非常重要,因為它可以通過類型的結(jié)構(gòu)空另,去獲取任意一個實例的相應屬性盆耽,而且這種方式遠比閉包更加簡單和緊湊。
覺得文章還不錯的話可以關(guān)注一下我的博客