@autoclosure
in Swift is a closure type that allows to ignore the curly braces and make it look like a regular expression. However, in more detail, it is still a closure. Its use allows us to improve the efficiency of our code. The@autoclosure
keyword may be new to you, and for many of us, it’s hard to come up with use cases for it. However, if you look closely, you will notice that it is used in the standard Swift API libraries you are using on a daily basis.
1. What is @autoclosure
?
It’s all in its name: @autoclosure
automatically creates a closure from an argument passed to a function. Converting an argument to a closure allows us to defer the actual request of the argument.
Let’s explain this in more detail using the following code example. In this example, we have created the debugLog
method and the struct Person
that we will print out:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | struct Person: CustomStringConvertible { let name: String var description: String { print("Asking for Person description.") return "Person name is (name)" } } let isDebuggingEnabled: Bool = false func debugLog(_ message: String) { /// You could replace this in projects with #if DEBUG if isDebuggingEnabled { print("[DEBUG] (message)") } } let person = Person(name: "Eval") debugLog(person.description) // Prints: // Asking for Person description. |
Even though debugging is turned off, struct Person
still required for its description. This is because the debugLog
message
debugLog
is calculated directly.
We can solve this problem by using the closure:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | let isDebuggingEnabled: Bool = false func debugLog(_ message: () -> String) { /// You could replace this in projects with #if DEBUG if isDebuggingEnabled { print("[DEBUG] (message())") } } let person = Person(name: "Eval") debugLog({ person.description }) // Prints: // - |
Closure message()
only called when debugging is enabled. You can see that now we need to pass the closure argument to the debugLog
method so it doesn’t look very good.
We can improve this code by using the @autoclosure
keyword:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | let isDebuggingEnabled: Bool = false func debugLog(_ message: @autoclosure () -> String) { /// You could replace this in projects with #if DEBUG if isDebuggingEnabled { print("[DEBUG] (message())") } } let person = Person(name: "Eval") debugLog(person.description) // Prints: // - |
The logic inside the debugLog
method remains the same and must still work with a closure. However, on the level of execution we can now pass the argument as if it were a regular expression. It looks both clean and familiar once we have optimized our debug logging code.
@autoclosure
allows to delay the actual computation of the argument, as we have seen before with lazy collections and lazy properties. In fact, if debugging isn’t enabled, we won’t compute debug descriptions like before!
2. Examples of standard Swift APIs using @autoclosure
Now that we know how @autoclosure
works, we can take a look at the standard API libraries that use this keyword.
A common example is the function assert(condition:message:file:line:)
. Its condition evaluates to #if DEBUG
true and its message is called only if the condition fails. Both arguments are auto closures. In fact, many API testing uses the @autoclosure
feature. For example here :
1 2 3 4 | public func XCTAssert(_ expression: @autoclosure () throws -> Bool, _ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line) { XCTAssertTrue(try expression(), message(), file: file, line: line) } |
3. Conclusion
@autoclosure
can be a great solution to prevent unnecessary work if the code isn’t actually being used. On the level of execution, everything looks the same, the more detail we have optimized our code.
Hopefully, this will help you with more efficient coding.
So my post here is over. Thank you for watching the article.