0Pricing
Swift Academy · Lesson

@dynamicMemberLookup

Build dynamic, type-safe member access.

@dynamicMemberLookup is a free Swift Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Swift Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

What Is Dynamic Member Lookup?

The @dynamicMemberLookup attribute lets a type respond to dot-syntax member access that is not declared as a real property. When you write value.something, Swift routes it to a special subscript you define.

This gives wrapper types a natural feel while keeping the access in one place.

@dynamicMemberLookup
struct Settings {
    private var storage: [String: String] = ["theme": "dark"]
    subscript(dynamicMember key: String) -> String? {
        storage[key]
    }
}

let s = Settings()
print(s.theme ?? "none")

The Required Subscript

A type marked @dynamicMemberLookup must implement subscript(dynamicMember:). When the parameter is a String, the member name you type becomes that string at runtime.

So config.timeout calls the subscript with the key "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)

String Lookup Is Not Type-Safe

String-based dynamic member lookup is flexible but loses compile-time checking. If you misspell a member, the code still compiles and you get whatever the subscript returns for a missing key.

Below, an unknown member quietly returns the default value rather than failing to build.

@dynamicMemberLookup
struct Config {
    let values: [String: Int]
    subscript(dynamicMember member: String) -> Int {
        values[member] ?? -1
    }
}

let config = Config(values: ["timeout": 30])
print(config.tmout)

Type-Safe Lookup With Key Paths

For safety you can make the subscript take a KeyPath instead of a string. Now the compiler verifies that the member exists on the wrapped type, restoring full checking while still forwarding access.

This pattern is ideal for wrapper types that hold a strongly typed value.

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)

Why the Key Path Version Is Safer

Because the subscript takes a KeyPath<User, T>, only members that actually exist on User are allowed. A typo like boxed.naem would now fail to compile, unlike the string version.

You get the convenience of dot syntax with none of the risk.

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)

Writable Dynamic Members

If the subscript provides a setter and takes a WritableKeyPath, you can also assign to forwarded members. The wrapper passes the write straight through to the wrapped value.

This makes a transparent mutable wrapper.

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)

Forwarding to a Wrapped Object

Dynamic member lookup is great for lightweight proxies that mostly forward to an inner value but add a little behavior. The subscript keeps the forwarding logic in one place.

Here a logging wrapper forwards reads and prints which member was accessed.

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)

Combining With Dynamic Callable Ideas

Dynamic member lookup composes well with generics. A single generic subscript can forward any property type, so one wrapper serves values of many shapes.

Here the same Proxy works whether the member is a String or an Int.

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)

String Lookup for JSON-Like Data

The string form still has a place: representing loosely structured data such as parsed JSON, where members are not known at compile time. Each access becomes a dictionary lookup.

Here a tiny JSON-like value exposes its fields through dot syntax.

@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)

Limitations to Keep in Mind

Dynamic member lookup only affects member access syntax; it does not add real stored properties. Tooling like autocomplete works for the key path form but not for arbitrary string keys.

Prefer the key path version whenever the underlying shape is known, and reserve the string version for genuinely dynamic data.

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)

A Practical Decorator

Putting it together, here is a measured wrapper that forwards every property read to its inner value while counting accesses. Real frameworks use this idea for observation and tracing.

The dot syntax on the wrapper feels identical to using the value directly.

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)

Quick Check: Type-Safe Dynamic Lookup

Recall which subscript parameter makes dynamic member lookup type-safe.

Recap: Dynamic Member Lookup

You learned how @dynamicMemberLookup works:

  • The attribute routes undeclared dot-syntax access to subscript(dynamicMember:).
  • A String parameter gives flexible but unchecked access, ideal for JSON-like data.
  • A KeyPath parameter restores full type safety and autocomplete.
  • A WritableKeyPath with a setter lets you forward writes through a wrapper.
  • It is a syntax convenience that pairs naturally with proxy and decorator patterns.

You now have the full key path toolkit: reading, writing, collection use, and dynamic forwarding.

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)

Frequently asked questions

Is the “@dynamicMemberLookup” lesson free?

Yes — the full text of “@dynamicMemberLookup” is free to read here on the web, and the Swift Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Swift Academy course, upgrade to CoddyKit PRO.

What will I learn in “@dynamicMemberLookup”?

Build dynamic, type-safe member access. You practise Swift Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Swift Academy?

No prior experience is required. Swift Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “@dynamicMemberLookup” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Swift Academy lesson?

Yes. Every Swift Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. KeyPath Basics
  2. Using KeyPaths in Collections
  3. Writable and Reference KeyPaths
  4. @dynamicMemberLookup
← Back to Swift Academy