Swift @escaping and Completion Handler

2020-02-07 14:11发布

问题:

I am trying to understand 'Closure' of Swift more precisely.

But @escaping and Completion Handler are too difficult to understand

I searched many Swift postings and official documents, but I felt it was still not enough.

This is the code example of official documents

var completionHandlers: [()->Void] = []

func someFunctionWithEscapingClosure(completionHandler: @escaping ()->Void){
    completionHandlers.append(completionHandler)
}

func someFunctionWithNoneescapingClosure(closure: ()->Void){
    closure()
}

class SomeClass{
    var x:Int = 10
    func doSomething(){
        someFunctionWithEscapingClosure {
            self.x = 100
            //not excute yet
        }
        someFunctionWithNoneescapingClosure {
            x = 200
        }
    }
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)

completionHandlers.first?() 
print(instance.x)

I heard that there are two ways and reasons using @escaping

First is for storing a closure, second is for Async operating purposes.

The following are my questions:

First, if doSomething executes then someFunctionWithEscapingClosure will executing with closure parameter and that closure will be saved in global variable array.

I think that closure is {self.x = 100}

How self in {self.x = 100} that saved in global variable completionHandlers can connect to instance that object of SomeClass ?

Second, I understanding someFunctionWithEscapingClosure like this.

To store local variable closure completionHandler to global variable 'completionHandlerswe using@escaping` keyword!

without @escaping keyword someFunctionWithEscapingClosure returns, local variable completionHandler will remove from memory

@escaping is keep that closure in the memory

Is this right?

Lastly, I just wonder about the existence of this grammar.

Maybe this is a very rudimentary question.

If we want some function to execute after some specific function. Why don't we just call some function after a specific function call?

What are the differences between using the above pattern and using an escaping callback function?

回答1:

First of all, I want to say "Very Good Question :)"

Completion Handler :

Assume the user is updating an app while using it. You definitely want to notify the user when it is done. You possibly want to pop up a box that says, “Congratulations, now, you may fully enjoy!”

So, how do you run a block of code only after the download has been completed? Further, how do you animate certain objects only after a view controller has been moved to the next? Well, we are going to find out how to design one like a boss. Based on my expansive vocabulary list, completion handlers stand for

Do stuff when things have been done

For more details, kindly visit this blog post.

This link gives me complete clarity about completion handlers (from a developer point of view it exactly defines what we need to understand).

@escaping closures:

When you are passing the closure in a function’s arguments, using it after the function’s body gets executed and returns the compiler back. When the function ends, the scope of the passed closure exist and have existence in memory, till the closure gets executed.

There are several ways to escaping the closure in containing function:

  • Storage: When you need to store the closure in the global variable, property or any other storage that exist in the memory past of the calling function get executed and return the compiler back.

  • Asynchronous execution: When you are executing the closure asynchronously on despatch queue, the queue will hold the closure in memory for you, can be used in future. In this case you have no idea when the closure will get executed.

When you try to use the closure in these scenarios the Swift compiler will show the error:

For more clarity about this topic you can check out this post on Medium.

Adding one more points , which every ios developer needs to understand :

  1. Escaping Closure : An escaping closure is a closure that’s called after the function it was passed to returns. In other words, it outlives the function it was passed to.
  2. Non-escaping closure : A closure that’s called within the function it was passed into, i.e. before it returns.

I hope you will get a good understanding from this link.

If still you have any questions (but be sure to read this link line by line first; it's very well explained) then feel free to share your comment.

Thanks, Keep posting if this answer needs to update



回答2:

Here's a small class of examples I use to remind myself how @escaping works.

class EscapingExamples: NSObject {

    var closure: (() -> Void)?

    func storageExample(with completion: (() -> Void)) {
        //This will produce a compile-time error because `closure` is outside the scope of this
        //function - it's a class-instance level variable - and so it could be called by any other method at
        //any time, even after this function has completed. We need to tell `completion` that it may remain in memory, i.e. `escape` the scope of this
        //function.
        closure = completion
        //Run some function that may call `closure` at some point, but not necessary for the error to show up.
        //runOperation()
    }

    func asyncExample(with completion: (() -> Void)) {
        //This will produce a compile-time error because the completion closure may be called at any time
        //due to the async nature of the call which precedes/encloses it.  We need to tell `completion` that it should
        //stay in memory, i.e.`escape` the scope of this function.
        DispatchQueue.global().async {
            completion()
        }
    }

    func asyncExample2(with completion: (() -> Void)) {
        //The same as the above method - the compiler sees the `@escaping` nature of the
        //closure required by `runAsyncTask()` and tells us we need to allow our own completion
        //closure to be @escaping too. `runAsyncTask`'s completion block will be retained in memory until
        //it is executed, so our completion closure must explicitly do the same.
        runAsyncTask {
            completion()
        }
    }





    func runAsyncTask(completion: @escaping (() -> Void)) {
        DispatchQueue.global().async {
            completion()
        }
    }

}


回答3:

import UIKit
import Alamofire

// Model

class ShortlistCountResponse : Decodable {
    var response : String?
    var data : ShortlistcountData?

}
class ShortlistcountData : Decodable {

    var totalpropFavcount : Int?
    var totalprojFavcount : Int?

}

// Generic class Definition......

static func fetchGenericData<T: Decodable>(urlString: String,params : [String:Any], completion: @escaping (T) -> ()) {
        let url = urlString
        let headers = ["Content-Type": "application/x-www-form-urlencoded", "Accept":"application/json"]
        Alamofire.request(url, method: .post, parameters:params, encoding: URLEncoding.default, headers: headers).responseJSON { response in
            print(response.request?.urlRequest ?? "")
            print(params)
            print(response.data ?? "")
            print(response.value ?? "")
            switch(response.result) {
            case .success(_):
                if let data = response.data{
                    do {
                        let gotData = try JSONDecoder().decode(T.self, from: data)
                        completion(gotData)

                    }
                    catch let jsonErr {
                        print("Error serializing json:", jsonErr)
                        ActivityIndicator.dismissActivityView()

                    }
                    DispatchQueue.main.async {
                        ActivityIndicator.dismissActivityView()
                    }
                }
                break
            case .failure(_):
                print(response.result.error ?? "")
                ActivityIndicator.dismissActivityView()


                break

            }
        }
}

// fun call

override func viewDidLoad() {
    super.viewDidLoad()

            let userID = ""
            let languageID = ""
            let params = ["userID":userID,"languageID":languageID]
            var appDelegate: AppDelegate?
            Service.fetchGenericData(urlString: "your url...", params: params) { (shortlistCountResponse : ShortlistCountResponse) in
             print(shortListCountResponse.data.totalprojFavcount ?? 0)

            }
}