Apple opened a blog to write about and promote Swift, which is really rare in Apple's history. And there is a post there, which mentioned the @autoclosure
keyword.
@autoclosure
is an amazing creation of Apple, it "seems" more likely a hack for this language. The thing @autoclosure
does is encapsulating a statement into a closure, automatically. This slicks up your code almost all the time, turning it understandable and neat.
Consider we have a method, which accepts a closure as parameter. When the closure is evaluated as true
, print a string:
func logIfTrue(predicate: () -> Bool) {
if predicate() {
print("True")
}
}
And we have to write this code to call it:
logIfTrue({return 2 > 1})
It is annoying: What is that return
and there are ()
and {}
pairs which make the code difficult to understand.
Of course, we could simplify the closure in this situation. Here it would be no problem to omit return
, making the code above to:
logIfTrue({2 > 1})
One step further, there is a syntax called trailing closure. We can omit the parentheses as well:
logIfTrue{2 > 1}
Fairly well, but not enough. If a programmer just switches to Swift and has no idea about trailing closure, he may be confused with the brace. Now, if we use @autoclosure
, we can make the world better for everyone. Just adding @autoclosure
in front of the parameter type:
func logIfTrue(@autoclosure predicate: () -> Bool) {
if predicate() {
print("True")
}
}
Now, we can write this:
logIfTrue(2 > 1)
to call the logIfTrue
method. Swift will transform the statement 2 > 1
into a () -> Bool
closure behind the scene. So we can have a good and simple expression.
Another important use case is ??
operator. ??
can be used in nil
condition check. If the left-hand of this operator is a non-nil Optional, the unwrapped value would be returned, otherwise, the right-hand default value will be used. Just a simple example:
var level : Int?
var startLevel = 1
var currentLevel = level ?? startLevel
Here we do not set level
when we declare it, so it will be a nil
. In the currentLevel
statement, startLevel
is assigned to currentLevel
, since there is no value in level
. If you are a curious cat, you may already click into the definition of ??
, which contains two versions:
func ??<T>(optional: T?, @autoclosure defaultValue: () -> T?) -> T?
func ??<T>(optional: T?, @autoclosure defaultValue: () -> T) -> T
In the example, we are using the latter one. Although it seems we are passing startLevel
as a simple Int
, it is a () -> Int
in fact, and is wrapped automatically by the @autoclosure
feature. Think about how ugly it will be if there is no @autoclosure
! With the hint of this method signature, we can try to implement this operator ourselves, like this:
func ??<T>(optional: T?, @autoclosure defaultValue: () -> T) -> T {
switch optional {
case .Some(let value):
return value
case .None:
return defaultValue()
}
}
Think deeper, and you may have a question about this operator: Why does not this operator just accept a T
as right-hand parameter? Is it not simpler by just using T
instead of @autoclosure
? That is the greatest part of @autoclosure
. If we use T
, that means we need to prepare everything ready, both the left and right-hand parameters, before passing them to the ??
operator. However, once the left-hand value optional
is not nil
, the unwrapped value will be returned immediately. In most case, it will not be a problem, but consider the situation that we are using a very complex algorithm to calculate the defaultValue
. We have to prepare it every time using ??
operator, but in fact there is a possibility that we will not use it at all. What a waste! This could be avoided in fact. The key is delaying the calculating for defaultValue
, making it follow the nil
check. The answer is @autoclosure
it, so it will not be executed until optional
falls into the .None
case.
With @autoclosure
, we are not only bypassed the conditional checking and explicitly conversion, but also get a benefit in performance. But there is also limitation when using @autoclosure
. It cannot support method with parameter, in other words, you cannot mark @autoclosure
except for () -> T
. Another trap is the caller of your code might not notice you want to accept an autoclosure
parameter. When writing methods with @autoclosure
, it is better to keep it simple and clear. If the parameter might create an ambiguity or be difficult to understand, please make the choice of turning to a complete closure, so everyone could know what happens in the code.
Exercise
In Swift, the logical operator &&
and ||
are using @autoclosure
as well. Now, why not open your Playground and try to implement them yourself? I think you can master this feature by doing so.