Dynamic Membership in Swift iOS 06.08.2020

Dynamic membership was introduced in Swift 4.2, with additional features in Swift 5. It allows you to do two things:

  • Access a nonexistent property (a dynamic property) of an instance or type.
  • Treat a reference to an instance as the name of a nonexistent function (a dynamic function).

Imagine we have a class Movie that acts as a gatekeeper to a dictionary. We can to talk to a Movie instance like this:

let movie = Movie()
movie.title = "The Matrix"
movie.year = 1999

if let title = movie.title {
    print(title)
}

movie(remove: "The Matrix")

Here’s how the implementation works:

Dynamic properties. The type must be marked @dynamicMemberLookup, and must declare a subscript taking a single parameter that is either a string or a key path and has an external name dynamicMember. The subscript return type is up to you, and you can have multiple overloads distinguished by the return type. When other code accesses a nonexistent property of this type, the corresponding subscript function — the getter or, if there is one, the setter — is called with the name of the property as parameter.

Dynamic functions. The type must be marked @dynamicCallable, and must declare either or both of these methods, where the type T is up to you:

dynamicallyCall(withArguments:[T])

Other code uses an instance as a function name and calls it with a variadic parameter of type T. The variadic becomes an array. If T is Int, and the caller says myObject(1,2), then this method is called with parameter [1,2].

dynamicallyCall(withKeywordArguments:KeyValuePairs<String, T>)

Other code uses an instance as a function name and calls it with labeled arguments of type T. The label– value pairs become string–T pairs; if a label is missing, it becomes an empty string. If T is String, and the caller says myObject(label:"one", "two"), then this method is called with parameter ["label":"one", "":"two"].

So here’s the implementation of Movie. Its dynamic properties are turned into dictionary keys, and it can be called as a function with a "remove" label to remove a key:

@dynamicMemberLookup 
@dynamicCallable 
class Movie {
    var d = [String:String]()
    subscript(dynamicMember s:String) -> String? {
        get { d[s] }
        set { d[s] = newValue } 
    }
    func dynamicallyCall(withKeywordArguments kv s:KeyValuePairs<String, String>) {
        if kvs.count == 1 {
            if let (key,val) = kvs.first {
                if key == "remove" { 
                    d[val] = nil
                } 
            }
        }
    }
}