0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Subscript, Keypathでプロパティ参照の可読性アップ (Swift)

Posted at

Swiftでインスタンスのプロパティにアクセスする際の方法は、.プロパティ名がよく使われるかと思いますが、それ以外にも実はいくつか方法があります。
ここではそれらの方法のうち、覚えておくと可読性アップにつながるsubscriptとkeypathについて使い方、メリットを合わせて紹介していきます。

Subscript

Swift(に限らずですが)でDictionaryのプロパティにアクセスする際、以下のような書き方がなされると思います。

let foodPrices: [String: Int] = ["curry": 1000, "hamburger": 800, "pasta": 850]
print(foodPrices["curry"]) // 1000

実はこちらのような書き方を、インスタンスにおいてもすることができます。
それを行うのに必要なのが、subscriptです。
subscriptは以下に示す通り、computed propertyのように定義できます。setterを定義すれば、辞書型のようにプロパティの書き換えも可能です。

class Fruit {
    let id = UUID().uuidString
    let weight: Int
    var name: String
    var detail: [String: String]
    let producer: Producer

    init(weight: Int, 
         name: String,
         detail: [String : String],
         producer: Producer) {
        self.weight = weight
        self.name = name
        self.detail = detail
        self.producer = producer
    }

    // computed propertyのようにgetter, setterを定義できる。
    subscript(key: String) -> String {
        get {
            switch key {
                case "weight": 
                    return "\(weight)"
                case "name": 
                    return name
                default: 
                    return detail[key] ?? ""
            }
        }
        set(newValue) {
            switch key {
                case "name":
                    name = newValue
                default:
                    detail[key] = newValue
            }
        }
    }
}

class Producer {
    var name: String
    let age: Int

    init(name: String, 
         age: Int) {
        self.name = name
        self.age = age
    }
}

var apple = Fruit(weight: 100,
                  name: "Apple",
                  detail: ["prefecture": "Aomori",
                           "color": "green"],
                  producer: Producer(name: "aaa",
                                     age: 60))

// 辞書型のように["プロパティ名"]で値へのアクセスができる。
print(apple["name"]) // Apple
print(apple["color"]) // green
apple["color"] = "red"
print(apple["color"]) // red

.プロパティ名でアクセスする

また、dynamicMemberLookupと呼ばれる修飾子を用いると、通常のプロパティのようなアクセスが可能になります。

// 追加
@dynamicMemberLookup
class Fruit {
    ...
    // 追加
    subscript(dynamicMember key: String) -> String {
        return detail[key] ?? ""
    }
}

print(apple.prefecture) // Aomori

通常のプロパティのように書くとネストが深くなりすぎるが、頻繁に使用する値などにはこちらの方法が有効かと思います。

KeyPath

また同様にプロパティにアクセスしやすくする方法として、KeyPathと呼ばれる文法が存在します。
こちらは、以下のように記述することで、プロパティへの参照を保持できる方法です。

let age = \Fruit.producer.age
print(apple[keyPath: age]) // 60

subscript同様、値の更新も行うことができます。

let name = \Fruit.producer.name
print(apple[keyPath: name]) // aaa
apple[keyPath: name] = "fuga"
print(apple[keyPath: name]) // fuga

また、既存のKeyPathに新たなPathをappendすることができます。

let producerPath = \Fruit.producer
let namePath = producerPath.appending(path: \.name)
print(apple[keyPath: namePath]) // aaa

.プロパティ名でアクセスする

KeyPathの場合も、以下のような形でdynamicMemberLookupを用いることができます。

struct Price {
    let yen, dollar: Int
}

@dynamicMemberLookup
class Fruit {
    let id = UUID().uuidString
    let weight: Int
    var name: String
    var detail: [String: String]
    let producer: Producer
    let price: Price // 追加

    init(weight: Int, 
         name: String,
         detail: [String : String],
         producer: Producer,
         price: Price) {
        self.weight = weight
        self.name = name
        self.detail = detail
        self.producer = producer
        self.price = price // 追加
    }
    ...
    // 以下を追加。KeyPath<対象クラス名, KeyPath対象の型>の形で定義する。
    subscript<T>(dynamicMember keyPath: KeyPath<Price, T>) -> String {
        return "\(price[keyPath: keyPath])"
    }
}

var apple = Fruit(weight: 100,
                  name: "Apple",
                  detail: ["prefecture": "Aomori",
                           "color": "green"],
                  producer: Producer(name: "aaa",
                                     age: 60), 
                  price: Price(yen: 150, // 追加
                               dollar: 1))

print(apple.yen) // 150

subscriptとの使い分けが難しいところですが、KeyPathの大きな特徴は参照の保持ができることです。
Pathにappendしていくことができる特徴を活かし、既存の参照からネストの深いところまで次々に処理をしていく場合などに重宝するのではないか、と考えます。

まとめ

  • subscriptはクラスや構造体で、プロパティに辞書型のような["プロパティ名"]の書き方で値にアクセスするのに使われる。
  • KeyPathは値への参照を保持しながら、任意のプロパティにアクセスするのに使われる。
    • 既存の参照にさらに深いネストへの参照をappendしていくことができる。
  • dynamic member lookupを使うと、.プロパティ名でプロパティへのアクセスをすることができる。
    • さらに存在しないプロパティへのアクセスはコンパイルエラーの代わりにdefault値を返すので、型安全に.記法を使うことができる。

最後に

こちらは私が書籍で学んだ学習内容をアウトプットしたものです。
わかりにくい点、間違っている点等ございましたら是非ご指摘お願いいたします。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?