Optional Completion Handler with no parameters
Asked Answered
N

2

9

I basically want to have an optional completion handler that has no parameters that are passed back. Here is what I have so far but obviously it is wrong.

func refreshAccountData(type:String, completion: (()->(Void))?){
    //Network call is made
    Alamofire.request... {
        completion?()
        //Here is where I want to call the optional completion handler
    }

}

I don't want to pass any parameters in the completion block. I just want to know when the network call has been completed so I can refresh the UI. I don't want to refresh the UI here because I want this to act as a generic function for refreshing data. The reason I want the completion handler to be optional is because sometimes I don't need to do anything after the refresh is done.

I am very confused about what @escaping means as well. I usually have it when I do things like this but any clarity would be great. I have done some homework about it online but not found much that I really get.

Notepaper answered 14/2, 2018 at 20:27 Comment(4)
What's wrong with it? It is not obvious, I just used the same declaration without a compiler error..Sentimentalism
really? this is the best way to do it? I don't have to worry about anything relating to escaping and I don't need to force unwrap the completion?(). I believe Xcode makes me for unwrap the completion!()Notepaper
Why not simply func refreshAccountData(type:String, completion: @escaping ()->()){ and just call completion()? If you don't want to execute anything just pass am empty closure when calling your methodSultan
Another option is to set a default value to your completion handler so you can simply omit the closure when calling your method func refreshAccountData(type:String, completion: @escaping (()->()) = {}){Sultan
I
11

First, drop the brackets around Void type:

func refreshAccountData(type:String, completion: (() -> Void)?){
    //Network call is made
    Alamofire.request... {
        completion?() 
    }
}

If you force unwrap a closure call, you are basically saying that you know that there will always be a completion callback. But in your case you explicitly mentioned that you want an optional, so calling completion?() is what you want.

Regarding escaping, optional closures are implicitly escaped, so if you want a non-escaping version, you would have to use non-optional closure. Read the Escaping Closures section in docs to learn more about escaping. Basically, in your case you need an escaping closure, because you use completion in the asynchronous callback, which executes after the refreshAccountData finishes. But again, as I said, making the closure optional makes it implicitly escaping (read more in SO question).

Now you know that your completion is escaping - what does it mean? Simply put, if it is non-escaping, compiler will guarantee that the completion closure gets released after the refreshAccountData method finishes, thus in effect all the resources captured by the completion would be released, too. Escaping completion closure however can live longer that during the refreshAccountData call - meaning that it can possible create retain cycles (the closure might be keeping live strong references to self and other objects, which might be because of that retained).

Now in your case, since you want an optional completion closure, you have no other way than simply to accept it is escaping - so how can you make sure completion closure won't keep unwanted strong references? You can simply use capture list (see this for reference) when creating the completion to make sure that even self is just a weak reference and it won't be retained because of the closure:

refreshAccountData(type: "") { [weak self] in
    guard let self = self else { return }
    self.doSomething()
}
Ingressive answered 14/2, 2018 at 20:54 Comment(0)
J
1

Using a Default Value

I don't prefer to use optional for completion handlers. So I use the default value for solution. Thanks for @Leo Dabus's comment.

func refreshAccountData(type:String, completion: @escaping (()->()) = {}){ 

}

Call Without Copletion

Here is how you can call without a completion handler.

refreshAccountData(type: "SomeTypeHere")

Call With Copletion

refreshAccountData(type: "") {
    
}
Jalopy answered 7/9, 2022 at 7:44 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.