dynamicMemberLookupと仲良くなる
自身が保持しないプロパティをあたかも保持するかのように振る舞える機能(と理解した)
以下のような構造体を考えてみる.
struct Wrapper<Wrapped> {
var wrapped: Wrapped
init(wrapped: Wrapped) {
self.wrapped = wrapped
}
}
こちらは単にWrapped型のプロパティをwrapするだけ.
次はこれでString型の変数をwrapしてみよう
struct Wrapper<Wrapped> {
var wrapped: Wrapped
init(wrapped: Wrapped) {
self.wrapped = wrapped
}
}
+ let string = "Hello, World!"
+ let wrapper = Wrapper(wrapped: string)
この時,文字数を数えたいとする.
変数string
に対しては,String.count methodを呼べば良い.
string.count // 13
変数wrapper
に対しては,次のようにすると同様に文字数を数えられる.
wrapper.wrapped.count // 13
ただし上の書き方ではちょっと冗長に感じる.ここで次の記法でかけるようにしたい
wrapper.count
それを実現するのがdynamicMemberLookup
+ @dynamicMemberLookup
struct Wrapper<Wrapped> {
var wrapped: Wrapped
init(wrapped: Wrapped) {
self.wrapped = wrapped
}
+ subscript<T>(dynamicMember keyPath: KeyPath<Wrapped, T>) -> T {
+ get {
+ wrapped[keyPath: keyPath]
+ }
+ }
}
let string = "Hello, World!"
let wrapper = Wrapper(wrapped: string)
string.count
wrapper.wrapped.count
+ wrapper.count
@dynamicMemberLookup
はattributesの1つ.
くだけていうと,この文脈ではWrapper
型がdynamicMemberLookupの機能を有していますよ,とコンパイラーに教えてあげるための目印.(というふうに理解している)
subscript
はWrapped
型(この文脈ではString
型)を基点とするKeyPathを引数として受け取る.
これでwrapper.count
と簡潔にWrapped
型が持たないString.count
を簡潔に呼び出せるようになった.いわばwrapper: Wrapped
がwrapper: String
のように振る舞っているわけである.
先ほどはdynamicMemberの読み取りができたので,次にdynamicMemberの書き換えを試みたい.
wrapされるオブジェクトをもう少し複雑にして考えてみよう.
@dynamicMemberLookup
struct Wrapper<Wrapped> {
var wrapped: Wrapped
init(wrapped: Wrapped) {
self.wrapped = wrapped
}
subscript<T>(dynamicMember keyPath: KeyPath<Wrapped, T>) -> T {
get {
wrapped[keyPath: keyPath]
}
}
}
struct Car {
var name: String
var maxSpeed: Int
}
var wrappedCar = Wrapper(wrapped: Car(name: "Toyota", maxSpeed: 200))
wrappedCar.name // "Toyota"
wrappedCar.maxSpeed // 200
ここでwrappedCar.name
を書き換えたくなった.
wrappedCar.name = "Honda" // Cannot assign to property: 'wrappedCar' is immutable
このままではできない.なぜならget
を持つsubscript
のみ用意しているから.そこで次のsubscript
に置き換える.
@dynamicMemberLookup
struct Wrapper<Wrapped> {
var wrapped: Wrapped
init(wrapped: Wrapped) {
self.wrapped = wrapped
}
- subscript<T>(dynamicMember keyPath: KeyPath<Wrapped, T>) -> T {
- get {
- wrapped[keyPath: keyPath]
- }
- }
+ subscript<T>(dynamicMember keyPath: WritableKeyPath<Wrapped, T>) -> T {
+ get {
+ wrapped[keyPath: keyPath]
+ }
+ set {
+ wrapped[keyPath: keyPath] = newValue
+ }
+ }
}
こうすることで,次のように書き換えも可能になる.
var wrappedCar = Wrapper(wrapped: Car(name: "Toyota", maxSpeed: 200))
wrappedCar.name // "Toyota"
wrappedCar.maxSpeed // 200
wrappedCar.name = "Honda"
wrappedCar.name // "Honda"
他にもreferencewritablekeypathを引数にとるsubscript
や
get節, set節以外に_read節, _modify節もsubscriptにかけるらしい.また今度調べよう.