0Pricing
Swift Academy · レッスン

@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フィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. KeyPathの基礎
  2. コレクションでKeyPathを使う
  3. 書き込み可能なKeyPathと参照KeyPath
  4. @dynamicMemberLookup
← Swift Academyに戻る