The main difference between callAsFunction
and @dynamicCallable
is that the former is more static and type-safe, while the latter is more dynamic and flexible. The primary use case for @dynamicCallable
is to interoperate with dynamic languages like Python and JavaScript, while callAsFunction
can be used to create types that behave like functions or closures.
Special method callAsFunction
allows you to call an object like a function. For example, if you have a struct that defines a callAsFunction
method, you can create an instance of that struct and call it with parentheses:
struct Adder {
func callAsFunction(_ x: Int, _ y: Int) -> Int {
return x + y
}
}
let add = Adder()
let sum = add(3, 4) // 7
You can also pass arguments and return values to the callAsFunction
method, just like a normal function. This can be useful for creating custom operators, wrappers, or DSLs.
A type can define multiple callAsFunction
methods:
struct Transformer {
func callAsFunction(_ input: String) -> String {
return input.uppercased()
}
func callAsFunction(_ input: Int) -> Int {
return input * 2
}
func callAsFunction(_ input: Bool) -> Bool {
return !input
}
}
let transformer = Transformer()
let result1 = transformer("hello") // equivalent to transformer.callAsFunction("hello")
print(result1) // HELLO
let result2 = transformer(10) // equivalent to transformer.callAsFunction(10)
print(result2) // 20
let result3 = transformer(true) // equivalent to transformer.callAsFunction(true)
print(result3) // false
Some of the environment values in SwiftUI are instances of types that define a callAsFunction
method, which allows you to call them like functions using a simple syntactic sugar. For example, the OpenURLAction
and DismissAction
types have a callAsFunction
method that opens a URL or dismisses a view, respectively.
struct ModalView: View {
@Environment(\.dismiss) private var dismiss
@Environment(\.openURL) private var openURL
var body: some View {
VStack {
Button("Get Help") {
if let url = URL(string: "https://www.example.com") {
openURL(url) // Implicitly calls openURL.callAsFunction(url)
}
}
Button("Dismiss") {
dismiss() // Implicitly calls dismiss.callAsFunction()
}
}
}
}
Attribute @dynamicCallable
lets you call a type like a function, using a simple syntactic sugar. It is mainly used for dynamic language interoperability, such as calling Python or JavaScript functions from Swift.
You mark a type with @dynamicCallable
attribute and implement one or both of these methods:
func dynamicallyCall(withArguments: <#Arguments#>) -> <#R1#>
func dynamicallyCall(withKeywordArguments: <#KeywordArguments#>) -> <#R2#>
These methods are called when the type is used as a function with positional or keyword arguments respectively.
A type that can be called with unnamed arguments:
@dynamicCallable
struct Sum {
func dynamicallyCall(withArguments args: [Int]) -> Int {
return args.reduce(0, +)
}
}
let sum = Sum()
let result1 = sum(1, 2, 3, 4)
// equivalent to sum.dynamicallyCall(withArguments: [1, 2, 3, 4])
print(result1) // 10
A type that can be called with named arguments:
@dynamicCallable
struct Greeting {
func dynamicallyCall(withKeywordArguments args: KeyValuePairs<String, String>) -> String {
var message = "Hello"
for (name, value) in args {
message += ", \(name): \(value)"
}
return message
}
}
let greeting = Greeting()
let result2 = greeting(name: "Alice", age: "25")
// equivalent to greeting.dynamicallyCall(withKeywordArguments: ["name": "Alice", "age": "25"])
print(result2) // Hello, name: Alice, age: 25
A type that can be called with either named or unnamed arguments:
@dynamicCallable
struct Calculator {
func dynamicallyCall(withArguments args: [Double]) -> Double {
return args[0] * args[1]
}
func dynamicallyCall(withKeywordArguments args: KeyValuePairs<String, Double>) -> Double {
return args.first?.value ?? 0
}
}
let calculator = Calculator()
let result3 = calculator(2.0, 3.0)
// equivalent to calculator.dynamicallyCall(withArguments: [2.0, 3.0])
print(result3) // 6.0
let result4 = calculator(answer: 42.0)
// equivalent to calculator.dynamicallyCall(withKeywordArguments: ["answer": 42.0])
print(result4) // 42.0