@dynamicMemberLookup
動的で型安全なメンバーアクセスを構築します。
「@dynamicMemberLookup」はCoddyKit上の無料Swift Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはSwift Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Swift Academyコースには全4レッスンが含まれています。
動的メンバー検索とは
@dynamicMemberLookup属性を使うと、型に実際のプロパティとして宣言されていないメンバーへのドット構文によるアクセスに対応できます。value.somethingと記述すると、Swiftはそれを定義した特別なサブスクリプトに転送します。
これにより、アクセス方法を1か所に集約しながら、ラッパー型を自然に扱えるようになります。
@dynamicMemberLookup
struct Settings {
private var storage: [String: String] = ["theme": "dark"]
subscript(dynamicMember key: String) -> String? {
storage[key]
}
}
let s = Settings()
print(s.theme ?? "none")必須のサブスクリプト
@dynamicMemberLookup属性を付けた型は、subscript(dynamicMember:)を実装する必要があります。パラメーターがStringの場合、入力したメンバー名が実行時にその文字列になります。
したがって、config.timeoutはキー"timeout"を指定してサブスクリプトを呼び出します。
@dynamicMemberLookup
struct Config {
let values: [String: Int]
subscript(dynamicMember member: String) -> Int {
values[member] ?? 0
}
}
let config = Config(values: ["timeout": 30, "retries": 3])
print(config.timeout)
print(config.retries)文字列による検索は型安全ではない
文字列ベースの動的メンバー検索は柔軟ですが、コンパイル時のチェックが失われます。メンバー名を間違えてもコードはコンパイルされ、存在しないキーに対してサブスクリプトが返す値を受け取ることになります。
次の例では、未知のメンバーにアクセスしてもビルドに失敗せず、デフォルト値が静かに返されます。
@dynamicMemberLookup
struct Config {
let values: [String: Int]
subscript(dynamicMember member: String) -> Int {
values[member] ?? -1
}
}
let config = Config(values: ["timeout": 30])
print(config.tmout)キーパスによる型安全な検索
安全性を高めるため、サブスクリプトが文字列ではなくKeyPathを受け取るようにできます。これでコンパイラーがラップ対象の型にメンバーが存在するかを検証でき、アクセスを転送しながら完全なチェックを復元できます。
このパターンは、型が明確に定義された値を保持するラッパー型に適しています。
struct User { let name: String; let age: Int }
@dynamicMemberLookup
struct Boxed {
let user: User
subscript<T>(dynamicMember path: KeyPath<User, T>) -> T {
user[keyPath: path]
}
}
let boxed = Boxed(user: User(name: "Ada", age: 36))
print(boxed.name)
print(boxed.age)キーパス版のほうが安全な理由
サブスクリプトがKeyPath<User, T>を受け取るため、Userに実際に存在するメンバーだけが許可されます。boxed.naemのようなタイプミスは、文字列版とは異なりコンパイルに失敗します。
リスクを伴わずに、ドット構文の便利さを得られます。
struct User { let name: String; let age: Int }
@dynamicMemberLookup
struct ReadOnly {
let value: User
subscript<T>(dynamicMember path: KeyPath<User, T>) -> T {
value[keyPath: path]
}
}
let r = ReadOnly(value: User(name: "Lin", age: 22))
print(r.name, r.age)書き込み可能な動的メンバー
サブスクリプトがsetterを提供し、WritableKeyPathを受け取る場合は、転送されたメンバーにも代入できます。ラッパーは書き込みをラップ対象の値へそのまま通します。
これにより、透過的で変更可能なラッパーを作成できます。
struct Profile { var name: String; var score: Int }
@dynamicMemberLookup
struct Wrapper {
var value: Profile
subscript<T>(dynamicMember path: WritableKeyPath<Profile, T>) -> T {
get { value[keyPath: path] }
set { value[keyPath: path] = newValue }
}
}
var w = Wrapper(value: Profile(name: "Mo", score: 0))
w.score = 99
print(w.value.score)ラップ対象のオブジェクトへの転送
動的メンバー検索は、内部の値へのアクセスをほとんど転送しながら、少しだけ動作を追加する軽量なプロキシに適しています。サブスクリプトによって、転送ロジックを1か所にまとめられます。
ここではログ出力用のラッパーが読み取りを転送し、どのメンバーにアクセスされたかを表示します。
struct Account { let id: Int; let balance: Double }
@dynamicMemberLookup
struct Logged {
let account: Account
subscript<T>(dynamicMember path: KeyPath<Account, T>) -> T {
let result = account[keyPath: path]
print("accessed a member")
return result
}
}
let logged = Logged(account: Account(id: 7, balance: 100))
print(logged.balance)動的呼び出し可能の考え方と組み合わせる
動的メンバー検索はジェネリクスとよく組み合わせられます。1つの汎用サブスクリプトで任意のプロパティ型を転送できるため、1つのラッパーでさまざまな形の値に対応できます。
ここでは、メンバーがStringの場合でもIntの場合でも、同じProxyが機能します。
struct Item { let title: String; let count: Int }
@dynamicMemberLookup
struct Proxy<Wrapped> {
let wrapped: Wrapped
subscript<T>(dynamicMember path: KeyPath<Wrapped, T>) -> T {
wrapped[keyPath: path]
}
}
let proxy = Proxy(wrapped: Item(title: "Book", count: 4))
print(proxy.title, proxy.count)JSONのようなデータに対する文字列検索
文字列形式にも用途があります。たとえば、パースしたJSONのように、コンパイル時にはメンバーが分からない柔軟な構造のデータを表現する場合です。アクセスするたびに辞書検索が行われます。
ここでは、小さなJSON風の値がドット構文を通じてフィールドを公開しています。
@dynamicMemberLookup
struct JSON {
let fields: [String: String]
subscript(dynamicMember key: String) -> String {
fields[key] ?? ""
}
}
let json = JSON(fields: ["city": "Oslo", "country": "Norway"])
print(json.city)
print(json.country)覚えておきたい制限事項
動的メンバー検索が影響するのはメンバーアクセスの構文だけであり、実際の格納プロパティが追加されるわけではありません。コード補完などのツールはキーパス形式では機能しますが、任意の文字列キーには対応できません。
基になる構造が分かっている場合はキーパス版を優先し、文字列版は本当に動的なデータのために使ってください。
struct Point { let x: Int; let y: Int }
@dynamicMemberLookup
struct Safe {
let point: Point
subscript<T>(dynamicMember path: KeyPath<Point, T>) -> T {
point[keyPath: path]
}
}
let safe = Safe(point: Point(x: 3, y: 4))
print(safe.x + safe.y)実用的なデコレーター
ここまでの内容を組み合わせると、内部の値へのすべてのプロパティ読み取りを転送しながら、アクセス回数を数える計測用ラッパーを作成できます。実際のフレームワークでは、この考え方が監視やトレースに使われています。
ラッパーに対するドット構文は、値を直接使う場合とまったく同じ感覚で扱えます。
struct Sensor { let temperature: Double; let humidity: Double }
@dynamicMemberLookup
class Tracked {
let sensor: Sensor
var reads = 0
init(_ s: Sensor) { sensor = s }
subscript<T>(dynamicMember path: KeyPath<Sensor, T>) -> T {
reads += 1
return sensor[keyPath: path]
}
}
let t = Tracked(Sensor(temperature: 21.5, humidity: 0.4))
print(t.temperature)
print(t.humidity)
print(t.reads)確認:型安全な動的検索
動的メンバー検索を型安全にするサブスクリプトのパラメーターを思い出してください。
復習:動的メンバー検索
@dynamicMemberLookupの仕組みを学びました。
- この属性により、宣言されていないドット構文によるアクセスが
subscript(dynamicMember:)に転送されます。 Stringパラメーターを使うと、柔軟ですがチェックされないアクセスが可能になり、JSONのようなデータに適しています。KeyPathパラメーターを使うと、完全な型安全性とコード補完が利用できます。- setterを備えた
WritableKeyPathを使うと、ラッパーを通じて書き込みを転送できます。 - これは構文上の便利な機能であり、プロキシパターンやデコレーターパターンと自然に組み合わせられます。
これで、キーパスの読み取り、書き込み、コレクションでの利用、動的な転送という一連の機能をすべて使えるようになりました。
struct User { let name: String }
@dynamicMemberLookup
struct Wrap {
let user: User
subscript<T>(dynamicMember path: KeyPath<User, T>) -> T {
user[keyPath: path]
}
}
print(Wrap(user: User(name: "Final")).name)よくある質問
「@dynamicMemberLookup」レッスンは無料ですか?
はい。「@dynamicMemberLookup」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Swift Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Swift Academyコースには全4レッスンが含まれています。
「@dynamicMemberLookup」で何を学びますか?
動的で型安全なメンバーアクセスを構築します。 ブラウザで直接実行するハンズオンコードでSwift Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Swift Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのSwift Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。
「@dynamicMemberLookup」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このSwift Academyレッスンでコードを書いて実行できますか?
はい。すべてのSwift Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- KeyPathの基礎
- コレクションでKeyPathを使う
- 書き込み可能なKeyPathと参照KeyPath
- @dynamicMemberLookup