Trouble with Non-Escaping Closures in Swift 3

Trouble with non-escaping closures in Swift 3

As already said, Optional closures are escaping. An addition though:

Swift 3.1 has a withoutActuallyEscaping helper function that can be useful here. It marks a closure escaping only for its use inside a passed closure, so that you don't have to expose the escaping attribute to the function signature.

Can be used like this:

extension Array {

private func someFunction(someClosure: (() -> Int)?) {
someClosure?()
}

func someOtherFunction(someOtherClosure: () -> Int) {
withoutActuallyEscaping(someOtherClosure) {
someFunction(someClosure: $0)
}
}
}

let x = [1, 2, 3]

x.someOtherFunction(someOtherClosure: { return 1 })

Hope this is helpful!

Closure use of non-escaping parameter - Swift 3 issue

updateCompleted is of type (_ completed: @escaping UpdateInProgressCompletion) -> (), which as it's a function parameter itself, means that it is non-escaping by default (note that 'non-escaping by default' behaviour is only applicable to function closure arguments, see this Q&A, as well as its dupe target on the topic).

Therefore in order to allow updateCompleted to escape, you need to mark (_ completed: @escaping UpdateInProgressCompletion) -> () as @escaping in your typealias:

typealias UpdateInProgressHandler = (@escaping (_ completed: @escaping UpdateInProgressCompletion) -> ()) -> ()

Why do closures require an explicit `self` when they're all non-escaping by default in Swift 3?

In Swift 3, all closures are non-escaping by default

No, in Swift 3, only closure function arguments (i.e function inputs that are functions themselves) are non-escaping by default (as per SE-0103). For example:

class A {

let n = 5
var bar : () -> Void = {}

func foo(_ closure: () -> Void) {
bar = closure // As closure is non-escaping, it is illegal to store it.
}

func baz() {
foo {
// no explict 'self.' required in order to capture n,
// as foo's closure argument is non-escaping,
// therefore n is guaranteed to only be captured for the lifetime of foo(_:)
print(n)
}
}
}

As closure in the above example is non-escaping, it is prohibited from being stored or captured, thus limiting its lifetime to the lifetime of the function foo(_:). This therefore means that any values it captures are guaranteed to not remain captured after the function exits – meaning that you don’t need to worry about problems that can occur with capturing, such as retain cycles.

However, a closure stored property (such as bar in the above example) is by definition escaping (it would be nonsensical to mark it with @noescape) as its lifetime not limited to a given function – it (and therefore all its captured variables) will remain in memory as long as the given instance remains in memory. This can therefore easily lead to problems such as retain cycles, which is why you need to use an explicit self. in order to make the capturing semantics explicit.

In fact, case in point, your example code will create a retain cycle upon viewDidLoad() being called, as someClosure strongly captures self, and self strongly references someClosure, as it's a stored property.

It's worth noting that as an extension of the "stored function properties are always escaping" rule, functions stored in aggregates (i.e structures and enumerations with associated values) are also always escaping, as there's no restrictions on what you do with such aggregates. As pointed out by pandaren codemaster, this currently includes Optional – meaning that Optional<() -> Void> (aka. (() -> Void)?) is always escaping. The compiler might eventually make this a special case for function parameters though, given that optional is already built on a lot of compiler magic.


Of course, one place where you would expect to be able to use the @noescape attribute is on a closure that’s a local variable in a function. Such a closure would have a predictable lifetime, as long as it isn’t stored outside of the function, or captured. For example:

class A {

let n = 5

func foo() {

let f : @noescape () -> Void = {
print(n)
}

f()
}
}

Unfortunately, as @noescape is being removed in Swift 3, this won't be possible (What's interesting is that in Xcode 8 GM, it is possible, but yields a deprecation warning). As Jon Shier says, we’ll have to wait for it to be re-added to the language, which may or may not happen.

Swift: Escaping closure captures non-escaping parameter 'onCompletion'

You have to mark both completion handlers with @escaping. Usually the compiler offers a fix

class RestApiManager: NSObject {
static let sharedInstance = RestApiManager()

let baseURL = "http://api.randomuser.me/"

func getRandomUser(onCompletion : @escaping (JSON) -> Void) {
makeHTTPGetRequest(path: baseURL, onCompletion: { json, err -> Void in
onCompletion(json)
})
}

func makeHTTPGetRequest(path: String, onCompletion: @escaping ServiceResponse) {
let request = NSMutableURLRequest(url : URL(string: path)! as URL)

let session = URLSession.shared

let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
let json:JSON = JSON(data as Any)
onCompletion(json, error as NSError?)
})
task.resume()

}
}

Swift-3: closure with escaping and non-escaping behaviour together

The rule for when you need @escaping is simple – if a closure function argument can escape the lifetime of the function call, it needs to be marked as @escaping (the compiler simply won't let you compile it otherwise).

In your example code, completionHandler is not marked as @escaping in f2 – therefore it cannot escape the lifetime of f2. Therefore it cannot possibly escape the lifetime of f1 either, thus you don't need to mark f1's completionHandler as @escaping.

If however, f2's completionHandler could escape the lifetime of f2, then you would have to mark both f2 and f1's parameters as @escaping as it could escape the lifetime of both calls.

Closure use of non-escaping parameter may allow it to escape

This is due to a change in the default behaviour for parameters of function type. Prior to Swift 3 (specifically the build that ships with Xcode 8 beta 6), they would default to being escaping – you would have to mark them @noescape in order to prevent them from being stored or captured, which guarantees they won't outlive the duration of the function call.

However, now @noescape is the default for function-typed parameters. If you want to store or capture such functions, you now need to mark them @escaping:

protocol DataServiceType {
func fetchData(location: String, completion: @escaping (DataFetchResult) -> Void)
func cachedData(location: String) -> Data?
}

func fetchData(location: String, completion: @escaping (DataFetchResult) -> Void) {
// ...
}

See the Swift Evolution proposal for more info about this change.

How to call non-escaping closure inside a local closure?

Non-parameter closures are @escaping, by default

"If localClosure is, by default, non-escaping, then why ..."

Based on the discussion in the comments below (thanks @Hamish), we can state the following facts regarding non-parameter closures in Swift 3.0:

  • They are, contrary to what one might believe, @escaping, by default. As @noescape is deprecated in Swift 3 (see e.g. Xcode 8 release notes or Swift evolution proposal SE-0103), this means that non-parameter closures cannot be made non-escaping without making use of deprecated methods.
  • As described in the following evolution thread, the lack of @noescape attribute for non-parameter closures is a missing feature (somewhat of a regression as this was not a limitation in Swift 2.2), but one that is not necessarily to be implemented in the future (if I'm to understand the answer by Apple dev. Jordan Rose in the linked evolution thread).
  • We may however (still) apply the deprecated @noescape attribute to a non-parameter closure to make it non-escaping, but will then notably be prompted with an incorrect warning (as below, emphasis mine), which has now been reported as a bug by @Hamish, see bug report SR-2969.

    "@noescape is default and is deprecated"

To summarize, localClosure is @escaping, which naturally means it cannot be allowed to wrap the non-escaping closure parameter closure of test(...).


[†] By non-parameter closures, I refer to all closures that are not parameters to a function, i.e., closures that are not supplied to a function as an argument.


As a side-note, which you possibly already knows given your question: we may naturally mark closure as @escaping if we'd wish to process/wrap it as in your example.

func test(closure: @escaping () -> ()) -> () -> () {
let escapingLocalClosure = { closure() }
return escapingLocalClosure
}


Related Topics



Leave a reply



Submit