Even though you may not know what an autoclosure is, I bet you have already used autoclosures without knowing it. Last week, we looked at the fatalError(_:file:line:)
function. What I didn't show you is the function declaration. This is what it looks like in Swift 3.
func fatalError(_: @autoclosure () -> String, file: StaticString, line: UInt)
Remember that we passed a string as the first argument of the fatalError(_:file:line:)
function.
fatalError("Unable to Load File From Bundle")
Looking at the function declaration, the type of the first parameter doesn't reflect this. The first parameter is of type () -> String
, a closure that accepts no arguments and returns a string. And the parameter type is prefixed with the @autoclosure
attribute. What is that about?
What Is an Autoclosure
The first parameter of the fatalError(_:file:line:)
function is an autoclosure as indicated by the @autoclosure
keyword. But what is an autoclosure?
An autoclosure allows you to pass an expression to a function as an argument.
This definition should get us started. With this in mind, it makes sense that we can pass a string to the fatalError(_:file:line:)
function. The string we pass to the function is an expression. The autoclosure wraps it in a closure. But why is that? What is the advantage of wrapping the expression in a closure?
By wrapping the expression in a closure, the function can decide if and when the closure is invoked. The moment the closure is invoked, the expression is evaluated.
As you can see, if used correctly, autoclosures are a powerful concept.
Anatomy of an Autoclosure
There are a few things you need to know about autoclosures. First, an autoclosure doesn't take any parameters. Second, autoclosures usually return a value, but that isn't always true. Later in this tutorial, we take a look at an example of an autoclosure that returns Void
.
In Swift 3, closures are non-escaping by default. If the autoclosure of a function needs to be escaping, you need to prefix the @autoclosure
attribute with the @escaping
attribute. I wrote about escaping closures and the @escaping
attribute last month.
When to Use Autoclosures
The benefit of autoclosures is that you don't need to wrap the expression in curly braces. That is what you would need to do if you were to pass in a closure instead of an autoclosure.
In the below example, you can see that the fatalError(_:file:line:)
function can accept an expression as its first argument thanks to autoclosures.
fatalError(response.error + " " + response.error)
Examples
Before I show you two examples of autoclosures, I need to emphasize that you won't find yourself using autoclosures very often. This is a quote from The Swift Programming Language.
It's common to call functions that take autoclosures, but it's not common to implement that kind of function.
Despite the above quote, it is important that you understand the concept underlying autoclosures even though you won't be implementing functions that use them very often.
Unbox
The first example I would like to show you is taken from one of my favorite libraries, Unbox, a lightweight JSON decoder created by John Sundell.
The following method is used to extract the value for a particular key or key path. What is important is that the method takes an autoclosure as its third parameter. The autoclosure returns the fallback value if no value for a particular key or key path is found.
func resolveRequiredValueForKey<R>(key: String, isKeyPath: Bool, fallbackValue: @autoclosure () -> R, transform: (T) -> R?) -> R {
if let value = self.resolveOptionalValueForKey(key: key, isKeyPath: isKeyPath, transform: transform) {
return value
}
self.unboxer.failForInvalidValue(invalidValue: self.unboxer.dictionary[key], forKey: key)
return fallbackValue()
}
The implementation isn't too complicated. What is of interest to us is the last line of the method.
return fallbackValue()
If no value was found, the autoclosure is invoked and the fallback value, which could be the result of an expression, is returned.
View Animations
The second example is a clever concept created by none other than Erica Sadun. In a blog post, Erica writes that autoclosures can help make the UIView
API for animations easier and more elegant.
She wants to go from this:
UIView.animate(withDuration: 2.5) {
self.view.backgroundColor = .orange
}
To this:
UIView.animate(withDuration: 2.5, self.view.backgroundColor = .orange)
Notice that we don't need to wrap the expression in a closure. How does this work?
To make this possible, we create an extension for UIView
and implement a class method that accepts an autoclosure.
extension UIView {
class func animate(withDuration duration: TimeInterval, _ animations: @escaping @autoclosure () -> Void) {
UIView.animate(withDuration: duration, animations: animations)
}
}
Remember that closure parameters, including autoclosures, are non-escaping by default. This means we need to prefix the @autoclosure
attribute with the @escaping
attribute.
What do you think? Doesn't this look better?
UIView.animate(withDuration: 2.5, self.view.backgroundColor = .orange)
Should You Use Autoclosures
Apple emphasizes that autoclosures should not be overused. Not only is it hard to find valid use cases, autoclosures also make it harder to understand the program flow. Don't overcomplicate your code with autoclosures if there is an easier alternative.