Swift Optional Escaping Closure

Swift optional escaping closure parameter

There is a SR-2552 reporting that @escaping is not recognizing function type alias. that's why the error @escaping attribute only applies to function types. you can workaround by expanding the function type in the function signature:

typealias Action = () -> ()

var action: Action? = { }

func doStuff(stuff: String, completion: (@escaping ()->())?) {
print(stuff)
action = completion
completion?()
}

func doStuffAgain() {
print("again")
action?()
}

doStuff(stuff: "do stuff") {
print("swift 3!")
}

doStuffAgain()

EDIT 1::

I was actually under a xcode 8 beta version where the bug SR-2552 was not resolved yet. fixing that bug, introduced a new one(the one you're facing) that is still open. see SR-2444.

The workaround @Michael Ilseman pointed as a temporary solution is remove the @escaping attribute from optional function type, that keep the function as escaping.

func doStuff(stuff: String, completion: Action?) {...}

EDIT 2::

The SR-2444 has been closed stating explicitly that closures in parameters positions are not escaping and need them to be marked with @escaping to make them escaping, but the optional parameters are implicitly escaping, since ((Int)->())? is a synonyms of Optional<(Int)->()>, optional closures are escaping.

Swift optional escaping closure

Clarification:

For understanding the case, implementing the following code would be useful:

typealias completion = () -> ()

enum CompletionHandler {
case success
case failure

static var handler: completion {
get { return { } }
set { }
}
}

func doSomething(handlerParameter: completion) {
let chObject = CompletionHandler.handler = handlerParameter
}

At the first look, this code seems to be legal, but it's not! you would get compile-time error complaining:

error: assigning non-escaping
parameter 'handlerParameter' to an @escaping closure

let chObject = CompletionHandler.handler = handlerParameter

with a note that:

note: parameter 'handlerParameter' is implicitly non-escaping func
doSomething(handlerParameter: completion) {

Why is that? the assumption is that the code snippet has nothing to do with the @escaping...

Actually, since Swift 3 has been released, the closure will be "escaped" if it's declared in enum, struct or class by default.

As a reference, there are bugs reported related to this issue:

  • Optional closure type is always considered @escaping.
  • @escaping failing on optional blocks.

Although they might not 100% related to this case, the assignee comments are clearly describe the case:

First comment:

The actual issue here is that optional closures are implicitly
@escaping right now.

Second comment:

That is unfortunately the case for Swift 3. Here are the semantics for
escaping in Swift 3:

1) Closures in function parameter position are
non-escaping by default

2) All other closures are escaping

Thus, all generic type argument closures, such as Array and Optional, are escaping.

Obviously, Optional is enum.

Also -as mentioned above-, the same behavior would be applicable for the classes and structs:

Class Case:

typealias completion = () -> ()

class CompletionHandler {
var handler: () -> ()

init(handler: () -> ()) {
self.handler = handler
}
}

func doSomething(handlerParameter: completion) {
let chObject = CompletionHandler(handler: handlerParameter)
}

Struct Case:

typealias completion = () -> ()

struct CompletionHandler {
var handler: completion
}

func doSomething(handlerParameter: completion) {
let chObject = CompletionHandler(handler: handlerParameter)
}

The two above code snippets would leads to the same output (compile-time error).

For fixing the case, you would need to let the function signature to be:

func doSomething( handlerParameter: @escaping completion)


Back to the Main Question:

Since you are expecting that you have to let the completion:(()->())? to be escaped, that would automatically done -as described above-.

Is optional closure always escaping? Should we use [weak self] or [unowned self] on it?

According to the compiler (which has the only "opinion" that matters for something like this), an optional closure parameter is always implicitly @escaping. Test this yourself by writing a function that takes an optional closure and mark it as @escaping. The compiler will inform you that it's already escaping.

As to whether you should capture self as weak or unowned, that depends. First I'd stay away from capturing self as unowned, unless you want your program to crash if you're wrong about the lifetime of self - and you might. I'd rather my program crash than silently do the wrong thing.

But that leaves whether to capture as weak. My opinion is yes, if you're in any doubt capturing a strong reference creating a reference cycle that would prevent it from properly deinitializing. But I wouldn't go so far as to say always or never do something. If you are able to reason about your object's life time so that you know the closure will be run and disposed of before the object should normally be deinitialized, then just capture as a strong reference. Also capture by strong reference if you purposefully want to extend the life of your object for the sake of the closure and you know that the closure will be executed and disposed of.

Also there is the case where you know that despite being implicitly @escaping that it doesn't actually escape. Usually that's for a function defined in your code base so you can look at the implementation, but really any time the closure is just a customization point for work that has to be done before the function you're calling returns, you can infer that it doesn't actually escape. That's not the case for completion handlers, but it might be for other things.

That's my opinion. I like to think it's an informed one, but others might disagree.

Passing optional closure to the View

Change your variable let to var.

var action: (() -> Void)? = nil

How to create a closure to use with @escaping

You're missing a return type on the closure (ie inside the last )):

typealias sessionDidReceiveMessageReplyHandlerHandler = (WCSession, [String : Any], @escaping ([String : Any]) -> Void) -> Void

Escaping closure captures non-escaping parameter 'function' Xcode says

I did this the following code, and it works as I expected! (But they aren't too clean)

        func getUsernameRequest(function: @escaping() -> Void) {
// send an API request to get the username
let url = "\(K.API.baseAPIEndpoint)/user"
AF.request(url, headers: headers).responseDecodable(of: GetUsernameModel.self) { response in
if let value = response.value {
self.username = value.username
function()
} else {
offlineBanner()
}
}
}

func getMessagesRequest(function: @escaping(@escaping()->Void)->Void, anotherFunc: @escaping()->Void) {
let postDetailUrl = "\(K.API.baseAPIEndpoint)/posts/\(postArray[indexPath.row].id)"
// send an API request to get all the associated messages and put them into messages array
AF.request(postDetailUrl, headers: headers).responseDecodable(of: PostDetailModel.self) { response in
if let value = response.value {
self.messages = value.messages
function(anotherFunc)
} else {
offlineBanner()
}
}
}

func performSegueToChat() -> Void {
self.performSegue(withIdentifier: K.Segue.GotoChat, sender: self)
}

getMessagesRequest(function: getUsernameRequest, anotherFunc: performSegueToChat)

Correct Syntax for Swift5.7 (Escaping closure captures non-escaping parameter 'block')

Make the block escaping

class func waitForDurationThenRunBlock(duration:TimeInterval,  block: @escaping((SCNNode?) -> Void) ) -> SCNAction {
let wait = SCNAction.wait(duration: duration)
let runBlock = SCNAction.run { (node) -> Void in
block(node)
}
return SCNAction.sequence([wait,runBlock])
}


Related Topics



Leave a reply



Submit