From a perspective of a framework writer it is important to provide clear and understandable documentation for the correct usage of their APIs. Swift allows us to write functions that are at some extent self documenting, and clearly define what the function will actually do.
since swift 3, closures in function parameters are by default nonescaping: you pass a closure as function parameter, and you know that this closure will be synchronously used within the function scope before this function returns.
declaring the closure to be @escaping as function parameter we say:
A closure is said to escape a function when the closure is passed as an argument to the function, but is called after the function returns. When you declare a function that takes a closure as one of its parameters, you can write
@escaping
before the parameterâs type to indicate that the closure is allowed to escape.
Quote from Swift documentation
All good so far.
func nonescaping(closure: () -> Void)
func escaping(closure: @escaping () -> Void)
What about optional closures?
func foo(closure: (() -> Void)? = nil)
Here we are saying that this closure parameter is not essential for the function to work, but what about its escaping nature?
In this case the closure is implicitly escaping because of the definition of Optionality in swift. But is that closure intended to escape the actual function scope? Is this closure executed before or after the function return?
Just by reading the function signature this is unknown, and we are forced by the compiler to assume that it is escaping the function (because it is assigned as associated value of the some
case of Optional
)
While we can argue that technically the closure is in fact escaping the function, maybe it is not the intention of the framework writer.
From this point of view
func foo(closure: (() -> Void)? = nil) {
....
closure?()
...
}
Is the same as
func foo(closure: @escaping () -> Void = { }) {
....
closure()
...
}
except that closure
is not escaping foo
in the second case, and if it wasn't for the definition of Optional
it wasn't escaping foo
in the first case either.
A solution to explicitly declare escapability of closures also in optional cases is by providing a default valid closure and not using Optional
func foo(closure: () -> Void = { }) {
....
closure()
...
}
but it some cases, this can lead to an ugly and hard to read function signature
func foo(closure: (Data?, Response?, Error?) -> Void = { _, _, _ in }) {
....
closure(data, response, nil)
...
}
and sometimes can also be error prone
func foo(closure: () -> Int = { /* what should I return here? */ }) {
....
//how to know that it is default?
?? = closure()
...
}
@unrequired keyword
An idea might be to stop this ambiguity for optional closures by introducing a new keyword @unrequired
var closure: (()->Int)?
func escaping(closure: @escaping @unrequired () -> Int) {
...
self.closure = closure
...
}
func nonEscaping(closure: @unrequired () -> Int) {
...
let value: Int
if closure is Empty {
value = 0
} else { value = closure() }
...
}
that would be equivalent to
func escaping(closure: @escaping () -> Int = Empty)
func nonEscaping(closure: () -> Int = Empty)
This would avoid the need of using the existent Optional
eliminating the ambiguity for escapability of the closure in relation to the function.
For the definition of Empty
I'll need your help. Ideally it should be a special Type like
Never
, that can represent Any type of empty closure and returns Never if the closure has a return type different than Void