withoutActuallyEscaping(_:do:)
Allows a nonescaping closure to temporarily be used as if it were allowed to escape.
func withoutActuallyEscaping<ClosureType, ResultType>(_ closure: ClosureType, do body: (ClosureType) throws -> ResultType) rethrows -> ResultType
Parameters
- closure
A nonescaping closure value that is made escapable for the duration of the execution of the
body
closure. Ifbody
has a return value, that value is also used as the return value for thewithoutActuallyEscaping(_:do:)
function.- body
A closure that is executed immediately with an escapable copy of
closure
as its argument.
Returns
The return value, if any, of the body
closure.
You can use this function to call an API that takes an escaping closure in a way that doesn’t allow the closure to escape in practice. The examples below demonstrate how to use withoutActuallyEscaping(_:do:)
in conjunction with two common APIs that use escaping closures: lazy collection views and asynchronous operations.
The following code declares an allValues(in:match:)
function that checks whether all the elements in an array match a predicate. The function won’t compile as written, because a lazy collection’s filter(_:)
method requires an escaping closure. The lazy collection isn’t persisted, so the predicate
closure won’t actually escape the body of the function; nevertheless, it can’t be used in this way.
func allValues(in array: [Int], match predicate: (Int) -> Bool) -> Bool {
return array.lazy.filter { !predicate($0) }.isEmpty
}
// error: closure use of non-escaping parameter 'predicate'...
withoutActuallyEscaping(_:do:)
provides a temporarily escapable copy of predicate
that can be used in a call to the lazy view’s filter(_:)
method. The second version of allValues(in:match:)
compiles without error, with the compiler guaranteeing that the escapablePredicate
closure doesn’t last beyond the call to withoutActuallyEscaping(_:do:)
.
func allValues(in array: [Int], match predicate: (Int) -> Bool) -> Bool {
return withoutActuallyEscaping(predicate) { escapablePredicate in
array.lazy.filter { !escapablePredicate($0) }.isEmpty
}
}
Asynchronous calls are another type of API that typically escape their closure arguments. The following code declares a perform(_:simultaneouslyWith:)
function that uses a dispatch queue to execute two closures concurrently.
func perform(_ f: () -> Void, simultaneouslyWith g: () -> Void) {
let queue = DispatchQueue(label: "perform", attributes: .concurrent)
queue.async(execute: f)
queue.async(execute: g)
queue.sync(flags: .barrier) {}
}
// error: passing non-escaping parameter 'f'...
// error: passing non-escaping parameter 'g'...
The perform(_:simultaneouslyWith:)
function ends with a call to the sync(flags:execute:)
method using the .barrier
flag, which forces the function to wait until both closures have completed running before returning. Even though the barrier guarantees that neither closure will escape the function, the async(execute:)
method still requires that the closures passed be marked as @escaping
, so the first version of the function does not compile. To resolve these errors, you can use withoutActuallyEscaping(_:do:)
to get copies of f
and g
that can be passed to async(execute:)
.
func perform(_ f: () -> Void, simultaneouslyWith g: () -> Void) {
withoutActuallyEscaping(f) { escapableF in
withoutActuallyEscaping(g) { escapableG in
let queue = DispatchQueue(label: "perform", attributes: .concurrent)
queue.async(execute: escapableF)
queue.async(execute: escapableG)
queue.sync(flags: .barrier) {}
}
}
}