KeyPath 最佳實踐

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)共享的值類型

出處:Swift Talk #61 | Swift Talk #62

這應該算是這篇文章里面最 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

上面我們可以看到 ageVarjohnVar 分割出來之后,它的狀態(tài)依舊跟 johnVar 保持一致腻异,這是因為 Var 的 init 方法里使用 block 捕獲了 x 這個變量进副,也就相當于作為 inout 參數(shù)傳入了進去,這個時候 x 會存放在堆區(qū)悔常。

并且使用 subscript 生成了 ageVar 之后影斑,ageVar 使用的 init 的方法只是在原本的 _get_set 方法外面再包了一層,所以 ageVar 修改值的時候机打,也是使用了原本 johnVar 一樣的 _set矫户,修改了最初 johnVar 初始化時使用的 x。換句話說残邀,ageVarjohnVar 使用的都是堆區(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)注一下我的博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末扼菠,一起剝皮案震驚了整個濱河市摄杂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌循榆,老刑警劉巖析恢,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異冯痢,居然都是意外死亡氮昧,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門浦楣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來袖肥,“玉大人,你說我怎么就攤上這事振劳∽底椋” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵历恐,是天一觀的道長寸癌。 經(jīng)常有香客問我,道長弱贼,這世上最難降的妖魔是什么蒸苇? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮吮旅,結(jié)果婚禮上溪烤,老公的妹妹穿的比我還像新娘。我一直安慰自己庇勃,他們只是感情好檬嘀,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著责嚷,像睡著了一般鸳兽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上罕拂,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天揍异,我揣著相機與錄音全陨,去河邊找鬼。 笑死蒿秦,一個胖子當著我的面吹牛烤镐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播棍鳖,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼炮叶,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了渡处?” 一聲冷哼從身側(cè)響起镜悉,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎医瘫,沒想到半個月后侣肄,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡醇份,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年稼锅,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片僚纷。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡矩距,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出怖竭,到底是詐尸還是另有隱情锥债,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布痊臭,位于F島的核電站哮肚,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏广匙。R本人自食惡果不足惜允趟,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鸦致。 院中可真熱鬧潮剪,春花似錦、人聲如沸蹋凝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鳍寂。三九已至,卻和暖如春情龄,著一層夾襖步出監(jiān)牢的瞬間迄汛,已是汗流浹背捍壤。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留鞍爱,地道東北人鹃觉。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像睹逃,于是被迫代替她去往敵國和親盗扇。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫沉填、插件疗隶、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,022評論 4 62
  • 立秋之后,天空就有點高遠了翼闹。 中午斑鼻,吃過飯后,看著天有點陰猎荠,不熱坚弱,就想著出去走走。前些日子由于暑熱難耐关摇,好久沒出去...
    蒼山暮雪閱讀 420評論 3 7
  • 《老光棍布盧姆費爾德》是卡夫卡荒誕的短篇小說荒叶,常被認為與《變形記》和美國作家卡佛的作品有互文性。 兩個憑空出現(xiàn)的詭...
    宋偲瑄閱讀 6,957評論 0 4
  • 你以為套路只有在人與人之間有嗎拒垃? 你錯了停撞。珠寶中的套路才是最深的。 用對了悼瓮,美cry戈毒;用錯了,丑cry横堡! 套路一:...
    珠寶大課堂閱讀 286評論 0 0