UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

How to handle unknown properties and methods using @dynamicMemberLookup

Swift version: 5.6

Paul Hudson    @twostraws   

Swift has always had strong focus on type safety, but sometimes you need to be able to work with data where the structure isn’t known ahead of time.

To handle this situation, Swift has an attribute called @dynamicMemberLookup, which instructs Swift to call a subscript method when accessing properties. This subscript method, subscript(dynamicMember:), is required when using the @dynamicMemberLookup attribute – you’ll get passed the string name of the property that was requested, and can return any value you like.

To help you understand the basics, here’s an example that creates a Person struct that reads its values from a dictionary:

@dynamicMemberLookup
struct Person {
    subscript(dynamicMember member: String) -> String {
        let properties = ["name": "Taylor Swift", "city": "Nashville"]
        return properties[member, default: ""]
    }
}

The @dynamicMemberLookup attribute requires the type to implement a subscript(dynamicMember:) method to handle the work of dynamic member lookup. As you can see, I’ve written one that accepts the member name as string and returns a string, and internally it just looks up the member name in a dictionary and returns its value.

That struct allows us to write code like this:

let person = Person()
print(person.name)
print(person.city)
print(person.nameOfPet)

That will compile cleanly and run, even though name, city, and nameOfPet do not exist as properties on the Person type. Instead, they are all looked up at runtime: that code will print “Taylor Swift” and “Nashville” for the first two calls to print(), then an empty string for the final one because our dictionary doesn’t store anything for nameOfPet.

This subscript(dynamicMember:) method must return a string, which is what enforces Swift’s type safety – even though you’re still dealing with dynamic data, Swift will ensure you get back what you expected.

If you want multiple different types, just implement different subscript(dynamicMember:) methods:

@dynamicMemberLookup
struct Employee {
    subscript(dynamicMember member: String) -> String {
        let properties = ["name": "Taylor Swift", "city": "Nashville"]
        return properties[member, default: ""]
    }

    subscript(dynamicMember member: String) -> Int {
        let properties = ["age": 26, "height": 178]
        return properties[member, default: 0]
    }
}

Now that any property can be accessed in more than one way, it’s important that you are clear which one should be used. That might be implicit, for example if you send the return value into a function that accepts only strings, or it might be explicit, like this:

let employee = Employee()
let age: Int = employee.age

Either way, Swift must know for sure which subscript will be called.

You can also overload subscript to return closures:

@dynamicMemberLookup
struct User {
    subscript(dynamicMember member: String) -> (_ input: String) -> Void {
        return {
            print("Hello! I live at the address \($0).")
        }
    }
}

let user = User()
user.printAddress("123 Swift Street")

When that’s run, user.printAddress returns a closure that prints out a string, and the ("123 Swift Street") part immediately calls it with that input.

Using dynamic member subscripting in a type that has also regular properties and methods will result in those properties and methods always being used in place of the dynamic member. For example, we could define a Singer struct with a built-in name property alongside a dynamic member subscript:

struct Singer {
    public var name = "Ed Sheeran"

    subscript(dynamicMember member: String) -> String {
        return "Taylor Swift"
    }
}

let singer = Singer()
print(singer.name)

That code prints “Ed Sheeran”, because the name property will always be used rather than the dynamic member subscript.

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Available from iOS 8.0

Similar solutions…

About the Swift Knowledge Base

This is part of the Swift Knowledge Base, a free, searchable collection of solutions for common iOS questions.

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 5.0/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.