🎓 A1Learner
🔍
0 of 27 lessons completed

Swift Dynamic Member Lookup

Dynamic member lookup lets a type answer member access with a subscript instead of declared properties. The member name arrives as a string, and the type decides which value that name stands for.

Members answered from a dictionary

The Settings type stores its numbers in one dictionary and declares no properties for them. Reading a member sends its name into the subscript, and a name with no stored entry answers the default of zero.

@dynamicMemberLookup
struct Settings {
    var values: [String: Int]
    subscript(dynamicMember member: String) -> Int {
        return values[member, default: 0]
    }
}
let settings = Settings(values: ["volume": 7, "brightness": 5])
print(settings.volume)
print(settings.brightness)
print(settings.contrast)

A string fallback for missing names

The Profile type answers members with text instead of numbers. A stored name prints its text, and a name that was never stored prints the fallback word.

@dynamicMemberLookup
struct Profile {
    var fields: [String: String]
    subscript(dynamicMember member: String) -> String {
        return fields[member] ?? "Unknown"
    }
}
let profile = Profile(fields: ["name": "Ana", "city": "Denver"])
print(profile.name)
print(profile.city)
print(profile.country)

Writing through a dynamic member

This Scores subscript has a getter and a setter over its dictionary. Assigning through dot syntax stores the number under that member name, and reading an unwritten name still answers zero.

@dynamicMemberLookup
struct Scores {
    var values: [String: Int]
    subscript(dynamicMember member: String) -> Int {
        get {
            return values[member, default: 0]
        }
        set {
            values[member] = newValue
        }
    }
}
var scores = Scores(values: [:])
scores.math = 90
scores.art = 75
print(scores.math)
print(scores.art)
print(scores.music)

One type, another value kind

The Prices type uses the same pattern with prices instead of counts. Stored names print their price, and a missing name prints the zero fallback in the same shape.

@dynamicMemberLookup
struct Prices {
    var values: [String: Double]
    subscript(dynamicMember member: String) -> Double {
        return values[member] ?? 0.0
    }
}
let prices = Prices(values: ["apple": 1.5, "pear": 2.25])
print(prices.apple)
print(prices.pear)
print(prices.plum)

Example

@dynamicMemberLookup
struct Metrics {
    var values: [String: Int]
    subscript(dynamicMember member: String) -> Int {
        get {
            return values[member, default: 0]
        }
        set {
            values[member] = newValue
        }
    }
}
var metrics = Metrics(values: ["visits": 120])
metrics.signups = 30
metrics.visits += 5
print(metrics.visits)
print(metrics.signups)
print(metrics.sales)

Key points

  • The dynamicMemberLookup attribute marks a type as answering member access through a subscript.
  • The subscript takes the dynamicMember label, and the member name arrives as a string.
  • A member that was never declared as a property can still be read with plain dot syntax.
  • The subscript decides what a missing name returns, so a fallback keeps every lookup safe.
  • A dynamic member subscript with a setter lets dot syntax write new values into the storage.
  • The storage stays an ordinary dictionary, so lookups follow the keys that were stored.

In this example, the Metrics type starts with one stored entry and no declared members at all. The signup assignment writes a new entry through the dynamic member setter, and adding five to visits reads and writes that entry through the same subscript. The sales line prints zero because that name was never stored and the getter answers its default.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

A dynamic member lookup reads a member name that has no stored entry. What decides the value that comes back?