Dynamic membership was introduced in Swift 4.2, with additional features in Swift 5. It allows you to do two things:
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 } } } } }