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
}
}
}
}
}