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値を返すので、型安全に
.
記法を使うことができる。
- さらに存在しないプロパティへのアクセスはコンパイルエラーの代わりにdefault値を返すので、型安全に
最後に
こちらは私が書籍で学んだ学習内容をアウトプットしたものです。
わかりにくい点、間違っている点等ございましたら是非ご指摘お願いいたします。