Help us understand the problem. What is going on with this article?

Swift4のKeyPathを学ぶ

More than 1 year has passed since last update.

はじめに

KeyPathはSwift4から導入されました。
同じくSwift4から導入されたKVOのコードとともに見ることもあり、これは何だ?思った記憶があります。その時はKVOのクロージャ採用【Qiita記事参照】に気を取られていてあまり深く考えなかったのですが、今回 try! Swift 2019でKeyPathの発表1があったため、改めて資料を参考にしながらXcodeで動かしてみました。

環境

Xcode10.1
Swift4.2

KeyPathの定義

KeyPathの公式ドキュメントはこちらです。
https://developer.apple.com/documentation/swift/swift_standard_library/key-path_expressions
https://developer.apple.com/documentation/swift/keypath

これによるとKeyPathには、いくつかの種類があります。

Class Declaration
KeyPath class KeyPath<Root, Value> : PartialKeyPath<Root> (リードオンリー)
PartialKeyPath class PartialKeyPath<Root> : AnyKeyPath (リードオンリー)KeyPath のsuper class
AnyKeyPath class AnyKeyPath (リードオンリー)PartialKeyPath のsuper class
WritableKeyPath class WritableKeyPath<Root, Value> : KeyPath<Root, Value> 値の読み書きをサポートするキーパス。
ReferenceWritableKeyPath class ReferenceWritableKeyPath<Root, Value> : WritableKeyPath<Root, Value> 参照セマンティクスを使用して値の読み書きをサポートするキーパス。

下記の(1)ようにKeyPathを記述するとUserのnameにアクセスできます。

Swift
    func testKeyPathBasic2()
    {
        struct User {
            var name: String
        }
        var player = User(name: "Lupin")
        let namePath = \User.name // <--- (1)
        print(String(describing: type(of: namePath)))  // WritableKeyPath<User, String>
        player[keyPath: namePath] = "Goemon" // <--- (2)

        XCTAssertTrue(player.name == "Goemon")
    }

ここで(1)のnamePathの型を見ると KeyPath<User, String> ではなく、WritableKeyPath<User, String> であることが分かります。
KeyPathはリードオンリーのため、WritableKeyPathにしないと(2)でエラーになるからです。

KeyPath Composition

KeyPathは下記のようにパスを構成することができます。これによって複雑な構造体の変数にアクセスすることもできます。

Swift
    func testKeyPathBasic3()
    {
        struct User {
            var name: Name
        }
        struct Name {
            var first: String
            var last: String
        }

        let player = User(name: Name(first: "Lupin", last: "the 3rd" ))
        let namePath = \User.name
        let firstPath = \Name.first
        let firstNamePath = namePath.appending(path: firstPath) // ここ

        let firstName = player[keyPath: firstNamePath]

        XCTAssertTrue(firstName == "Lupin")
    }

上記のパスの連結はそもそも下記のように書けます。

Swift
        let firstNamePath2 = \User.name.first  // パスを記述する
        let firstName2 = player[keyPath: firstNamePath2]

KeyPathの応用

try! Swift 2019で取り上げられたKeyPathの手法に関しては正直メリットがそれほど伝わってきませんでした。(英語理解力不足もありますが)
しかしながら下記のようなKeyPathを使った拡張は有効だと思います。

Swift
enum SortOrder
{
    case ascending
    case descending
}

extension Sequence
{
    func sorted<T: Comparable>(by keyPath: KeyPath<Element, T>, order: SortOrder = .ascending) -> [Element] {
        return sorted { a, b in
            switch order
            {
            case .ascending:
                return a[keyPath: keyPath] < b[keyPath: keyPath]
            case .descending:
                return a[keyPath: keyPath] > b[keyPath: keyPath]
            }
        }
    }
}

このsortedメソッドは、Comparableな型の変数のKeyPathでソートします。

Swift
        struct User {
            var name: Name
        }
        struct Name {
            var first: String
            var last: String
        }

        let person1 = User(name: Name(first: "Lupin", last: "the 3rd" ))
        let person2 = User(name: Name(first: "Goemon", last: "the 13th" ))

User、Nameの変数でソートする場合は、通常は下記のようにsort関数を用います。

Swift
        var people = [person1, person2]
        let newPeople = people.sort{ $0.name.first > $1.name.first }

KeyPathを使用した場合は下記のようになります。

Swift
        let people = [person1, person2]
        let newPeople = people.sorted(by: \User.name.first)

KeyPathを用いると
1)対象になる配列はletで良い
2)sorted()という関数になっている
3)上記関数の引数がKeyPathでより内容が明快
という違いがあります。

軽微な違いではありますが、より可読性が高いコードとなっていると思います。

参考

このような拡張を行うライブラリーとしてKeyPathKitがあります。
まだ使用してなくて感想は書けませんが、ソースコードを読む限りでは同じような方向を向いた使いやすいライブラリーのようです。


  1. try! Swift 2019で取り上げられた「Keypath入門」 

BlueEventHorizon
⚔️ios engineer ❤️science fiction 💚XCode 🌲innovation and entrepreneurship 🏃‍♂️golang beginner
https://note.mu/k2moons
yumemi
みんなが知ってるあのサービス、実はゆめみが作ってます。スマホアプリ/Webサービスの企画・UX/UI設計、開発運用。Swift, Kotlin, PHP, Vue.js, React.js, Node.js, AWS等エンジニア・クリエイターの会社です。東京(三軒茶屋)/京都(四条烏丸)/札幌/大阪/福岡に展開中!Twitterで情報配信中https://twitter.com/yumemiinc
http://www.yumemi.co.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした