Swift之深入解析Key Paths的功能與應(yīng)用

      網(wǎng)友投稿 984 2025-04-01

      一、前言

      自從 Swift 剛開始就被設(shè)計(jì)為是編譯時(shí)安全和靜態(tài)類型后,它就缺少了那種經(jīng)常在運(yùn)行時(shí)語言中的動(dòng)態(tài)特性,比如 Object-C, Ruby 和 javaScript。舉個(gè)例子,在 Objective-C 中,我們可以很輕易的動(dòng)態(tài)去獲取一個(gè)對(duì)象的任意屬性和方法,甚至可以在運(yùn)行時(shí)交換它們的實(shí)現(xiàn)。

      雖然缺乏動(dòng)態(tài)性正是 Swift 如此強(qiáng)大的一個(gè)重要原因,它幫助我們編寫更加可以預(yù)測(cè)的代碼以及更大的保證了代碼編寫的準(zhǔn)確性, 但是有時(shí)候,能夠編寫具有動(dòng)態(tài)特性的代碼是非常有用的。

      值得慶幸的是,Swift 不斷獲取越來越多的更具動(dòng)態(tài)性的功能,同時(shí)還一直把它的關(guān)注點(diǎn)放在代碼的類型安全上,其中的一個(gè)特性就是 Key Paths,那么 Key Paths 是如何在 Swift 中工作的呢?它有哪些非常炫酷非常有用的事情值得讓我們?nèi)プ觯?/p>

      二、Key Paths 基礎(chǔ)

      Key Paths 基本上可以讓我們將任何實(shí)例屬性引用為一個(gè)單獨(dú)的值。因此,它們可以通過表達(dá)式傳遞,并使一段代碼能夠獲取或設(shè)置一個(gè)屬性而無需實(shí)際了解該屬性。

      Key Paths 有三種主要變種:

      KeyPath:提供對(duì)屬性的只讀訪問權(quán)限;

      WritableKeyPath:提供對(duì)具有值語義的可變屬性的讀寫訪問權(quán)限(因此所討論的實(shí)例也需要是可變的,以便允許的寫入);

      ReferenceWritableKeyPath: 只能與引用類型(例如類的實(shí)例)一起使用,并為任何可變屬性提供讀寫訪問權(quán)限。

      還有一些額外的 Key Paths 類型,即可以減少內(nèi)部代碼復(fù)制并幫助類型擦除,但我們將專注于本文中的主要類型。我們來深入查看如何使用 Key Paths,以及是什么讓它們變得有趣并具有潛在的強(qiáng)大功能。

      三、功能演示

      設(shè)我們正在構(gòu)建一個(gè)應(yīng)用程序,它可以讓用戶從 Web 閱讀文章,并且有一個(gè)用來代表一個(gè)這樣的文章的 Article 模型,看起來像這樣:

      struct Article { let id: UUID let source: URL let title: String let body: String }

      1

      2

      3

      4

      5

      6

      每當(dāng)我們使用這樣的模型的數(shù)組時(shí),從每個(gè)模型中提取一段數(shù)據(jù)來形成一個(gè)新的數(shù)組是很常見的。如下所示兩個(gè)例子,從一個(gè)文章數(shù)組中收集所有的 id 和來源:

      let articleIDs = articles.map {

      let articleIDs = articles.map { $0.id } let articleSources = articles.map { $0.source }

      .id } let articleSources = articles.map {

      let articleIDs = articles.map { $0.id } let articleSources = articles.map { $0.source }

      .source }

      1

      2

      雖然上面的方法完全有效,但由于我們只對(duì)從每個(gè)元素中提取單個(gè)值感興趣,因此實(shí)際上并不需要閉包的全部功能,因此使用 Key Paths 可能是非常合適的。

      我們將從擴(kuò)展 Sequence 開始來添加一個(gè)覆蓋 map,它采用一個(gè) Key Paths 而不是一個(gè)閉包。由于我們只對(duì)這個(gè)用例的只讀訪問感興趣,所以我們將使用標(biāo)準(zhǔn)的 KeyPath 類型,為了實(shí)際執(zhí)行數(shù)據(jù)提取,將使用給定的鍵路徑作為參數(shù)下標(biāo),如下所示:

      extension Sequence { func map(_ keyPath: KeyPath) -> [T] { return map {

      extension Sequence { func map(_ keyPath: KeyPath) -> [T] { return map { $0[keyPath: keyPath] } } }

      [keyPath: keyPath] } } }

      1

      2

      3

      4

      5

      如果使用的 Swift 5.2 或更高版本,則不再需要上述擴(kuò)展,因?yàn)楝F(xiàn)在可以將 Key Paths 自動(dòng)轉(zhuǎn)換為函數(shù)。

      通過以上擴(kuò)展,現(xiàn)在能夠使用一個(gè)非常好的和簡(jiǎn)單的語法來從任何序列中的每個(gè)元素中提取單個(gè)值,使得可以從之前轉(zhuǎn)換我們的示例:

      let articleIDs = articles.map(\.id) let articleSources = articles.map(\.source)

      1

      2

      這是非常炫酷的,但是,當(dāng) Key Paths 真正開始發(fā)光時(shí),它們用于形成稍微復(fù)雜的表達(dá)式,例如在排序一系列值時(shí)。標(biāo)準(zhǔn)庫能夠自動(dòng)對(duì)包含 Sortable 元素的任何序列進(jìn)行排序,但對(duì)于所有其它類型,我們必須提供自己的排序閉包。但是,使用 Key Paths,可以通過基于 Comparable 的 Key Paths 輕松添加用于對(duì)任何序列進(jìn)行排序的支持。就像之前一樣,我們將在序列 Sequence 協(xié)議中添加一個(gè)擴(kuò)展,將給定 Key Paths 轉(zhuǎn)換為排序表達(dá)式閉包:

      extension Sequence { func sorted(by keyPath: KeyPath) -> [Element] { return sorted { a, b in return a[keyPath: keyPath] < b[keyPath: keyPath] } } }

      1

      2

      3

      4

      5

      6

      7

      使用上面的擴(kuò)展,我們現(xiàn)在能夠快速而簡(jiǎn)單地對(duì)任何序列進(jìn)行排序,只需給出想要排序的 Key Paths 。如果我們正在構(gòu)建的應(yīng)用程序處理任何形式的可排序列表,例如包含播放列表的音樂應(yīng)用程序,這非常方便,因?yàn)槲覀儸F(xiàn)在自由地對(duì)列表進(jìn)行排序,甚至是嵌套:

      playlist.songs.sorted(by: \.name) playlist.songs.sorted(by: \.dateAdded) playlist.songs.sorted(by: \.ratings.worldWide)

      1

      2

      3

      這樣做的似乎只是簡(jiǎn)單地添加了一個(gè)語法糖,但可以制作一些更復(fù)雜的代碼處理的序列同時(shí)更容易閱讀,并且還可以幫助減少代碼復(fù)制,因?yàn)楝F(xiàn)在還能夠?yàn)槿魏螌傩灾赜孟嗤呐判虼a。

      四、不需要實(shí)例

      雖然適量的語法很好,但是 Key Paths 的真正威力來自于,它可以讓我們引用屬性而不必與任意的實(shí)例相關(guān)聯(lián)。延續(xù)使用之前的音樂主題,假設(shè)我們正在開發(fā)一個(gè)展示歌曲列表的 App,并且在 UI 中為這個(gè)列表配置 UITableViewCell,使用如下的配置類型:

      struct SongCellConfigurator { func configure(_ cell: UITableViewCell, for song: Song) { cell.textLabel?.text = song.name cell.detailTextLabel?.text = song.artistName cell.imageView?.image = song.albumArtwork } }

      1

      2

      3

      4

      5

      6

      7

      再次聲明,上面的代碼沒有一點(diǎn)問題,但是我們期望以這樣的方式渲染其她的模型的概率非常的高(非常多的 tableView 的 cells 嘗試著去渲染標(biāo)題,副標(biāo)題以及圖片而不用去管它們代表的是什么模型),因此讓我們看看,我們能否用 Key Paths 的威力去創(chuàng)建一個(gè)共享的配置實(shí)現(xiàn),讓他可以被任意的模型使用。

      創(chuàng)建一個(gè)名叫 CellConfigurator 的泛型,然后因?yàn)槲覀兿胍貌煌哪P腿ヤ秩静煌臄?shù)據(jù),所以將會(huì)給它提供一組基于 Key Paths 的屬性,先渲染其中的一個(gè)數(shù)據(jù):

      struct CellConfigurator { let titleKeyPath: KeyPath let subtitleKeyPath: KeyPath let imageKeyPath: KeyPath func configure(_ cell: UITableViewCell, for model: Model) { cell.textLabel?.text = model[keyPath: titleKeyPath] cell.detailTextLabel?.text = model[keyPath: subtitleKeyPath] cell.imageView?.image = model[keyPath: imageKeyPath] } }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      上面的實(shí)現(xiàn)優(yōu)雅的地方在于,我們現(xiàn)在可以為每個(gè)模型定制 CellConfigurator,使用相同的輕量的 Key Paths 語法,如下所示:

      let songCellConfigurator = CellConfigurator( titleKeyPath: \.name, subtitleKeyPath: \.artistName, imageKeyPath: \.albumArtwork ) let playlistCellConfigurator = CellConfigurator( titleKeyPath: \.title, subtitleKeyPath: \.authorName, imageKeyPath: \.artwork )

      1

      2

      3

      4

      5

      6

      7

      Swift之深入解析Key Paths的功能與應(yīng)用

      8

      9

      10

      11

      就像標(biāo)準(zhǔn)庫中的 map 和 sorted 等函數(shù)的操作一樣,曾經(jīng)可能會(huì)使用閉包去實(shí)現(xiàn) CellConfigurator。然而,通過 Key Paths 能夠使用一個(gè)非常好的語法去實(shí)現(xiàn)它,并且也不需要任何的訂制化的操作去不得不通過模型實(shí)例去處理,使它們變得更加的簡(jiǎn)單,更加的具有說服力。

      五、轉(zhuǎn)化為函數(shù)

      目前為止,僅僅使用 Key Paths 來讀取值,那么如何使用它們來動(dòng)態(tài)的寫值呢?在很多不同的代碼中,我們常常可以見到一些像下面的代碼一樣的列子,我們通過這段代碼來加載一系列的事項(xiàng),然后在 ListViewController 中去渲染它們,然后當(dāng)加載操作完成后,我們會(huì)簡(jiǎn)單的將加載的事項(xiàng)賦值給視圖控制器中的屬性:

      class ListViewController { private var items = [Item]() { didSet { render() } } func loadItems() { loader.load { [weak self] items in self?.items = items } } }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      我們看看通過 Key Paths 賦值能否讓上面的語法簡(jiǎn)單一點(diǎn),并且能夠移除我們經(jīng)常使用的 weak self 的語法(如果忘記對(duì) self 的引用前加上 weak 關(guān)鍵字的話,那么就會(huì)產(chǎn)生循環(huán)引用)。既然所有上面我們做的事情都是獲取傳遞給我們閉包的值,并將它賦值給視圖控制器中的屬性,那么如果我們真的能夠?qū)傩缘?setter 作為函數(shù)傳遞,會(huì)不會(huì)很酷呢?這樣的話,就可以直接將函數(shù)作為完成閉包傳遞給我們的加載方法,然后所有的事情都會(huì)正常執(zhí)行。

      為了實(shí)現(xiàn)這一目標(biāo),首先定義一個(gè)函數(shù),讓任意的可寫的轉(zhuǎn)化為一個(gè)閉包,然后為 Key Paths 設(shè)置屬性值。為此,我們將會(huì)使用 ReferenceWritableKeyPath 類型,因?yàn)橹幌氚阉拗茷橐妙愋停ǚ駝t只會(huì)改變本地屬性的值),給定一個(gè)對(duì)象,以及給這個(gè)對(duì)象設(shè)置 Key Paths,我們將會(huì)自動(dòng)將捕獲的對(duì)象作為弱引用類型,一旦函數(shù)被調(diào)用,就會(huì)給匹配 Key Paths 的屬性賦值。如下所示:

      func setter( for object: Object, keyPath: ReferenceWritableKeyPath ) -> (Value) -> Void { return { [weak object] value in object?[keyPath: keyPath] = value } }

      1

      2

      3

      4

      5

      6

      7

      8

      使用上面的代碼,可以簡(jiǎn)化之前的代碼,將弱引用的 self 去除,然后用看起來非常簡(jiǎn)潔的語法結(jié)尾:

      class ListViewController { private var items = [Item]() { didSet { render() } } func loadItems() { loader.load(then: setter(for: self, keyPath: \.items)) } }

      1

      2

      3

      4

      5

      6

      7

      到這里,非常酷有沒有?或許它還能變得更加的酷,當(dāng)上面的代碼跟更加先進(jìn)的函數(shù)式編程思想結(jié)合在一起的時(shí)候,如組合函數(shù)。因此我們現(xiàn)在可以將多個(gè) setter 函數(shù)和其它的函數(shù)鏈接在一起使用。

      六、使用 Key Paths 創(chuàng)建方便的 API

      Swift 的 Key Paths 可以構(gòu)建非常強(qiáng)大的 API,而且在調(diào)用站點(diǎn)上看起來非常漂亮和干凈,這里我們有一個(gè)擴(kuò)展,讓我們可以輕松組任何基于一個(gè) key path 的 Sequence:

      // This extension will let us group any Sequence (such as an // Array or a Set), based on a given key path. extension Sequence { func grouped(by keyPath: KeyPath) -> [T: [Element]] { // Using key path subscripting syntax, we can dynamically // access a member of a type based on a key path. return .init(grouping: self, by: { $0[keyPath: keyPath] }) } } func scan(_ string: String, using matchers: [Matcher]) { // The result is that our call sites become really clean, since // we can simply use a key path literal to group any sequence. let matchersByPattern = ( start: matchers.grouped(by: \.pattern.start), end: matchers.grouped(by: \.pattern.end) ) ... }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      Swift

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:Excel環(huán)形對(duì)比圖的做法有哪些
      下一篇:excel怎么復(fù)制上一行公式? excel自動(dòng)生成上一行的公式的教程
      相關(guān)文章
      国产亚洲福利精品一区二区| 亚洲精品无码永久在线观看男男| 亚洲人成色99999在线观看| 亚洲av丰满熟妇在线播放| 亚洲人成亚洲人成在线观看 | 国产亚洲综合视频| 亚洲国产精品美女久久久久| 亚洲日韩精品无码专区加勒比| 亚洲狠狠成人综合网| 99亚偷拍自图区亚洲| 亚洲人成网站免费播放| 亚洲色无码专区一区| 亚洲精品国产摄像头| 亚洲精品9999久久久久无码| 亚洲国产成人精品无码区花野真一| 亚洲精品久久无码| 亚洲AV无码成人精品区日韩| 麻豆亚洲AV成人无码久久精品| 亚洲成A人片77777国产| 亚洲国产精品综合久久一线 | jjzz亚洲亚洲女人| 亚洲男人的天堂一区二区| 久久99亚洲综合精品首页| 国产亚洲精品无码专区| 亚洲日韩精品一区二区三区无码| 亚洲综合图色40p| 久久精品国产精品亚洲艾草网| 亚洲毛片在线观看| 亚洲最新中文字幕| 国产亚洲精品影视在线| 亚洲欧美日韩中文高清www777| 久久久久亚洲精品无码网址色欲 | 亚洲a∨无码精品色午夜| 高清在线亚洲精品国产二区| 亚洲日本一区二区三区在线不卡| 亚洲熟妇无码乱子AV电影| 亚洲毛片在线观看| 亚洲资源最新版在线观看| 亚洲一日韩欧美中文字幕在线| 亚洲大尺度无码无码专线一区| 亚洲精品网站在线观看不卡无广告|