How to make async / await in Swift?

后端 未结 5 1991
别跟我提以往
别跟我提以往 2020-12-28 14:53

I would like to simulate async and await request from Javascript to Swift 4. I searched a lot on how to do it, and I thought I found the answer with DispatchQueue

相关标签:
5条回答
  • 2020-12-28 15:42

    In iOS 13 and up, you can now do this using Combine. Future is analogous to async and the flatMap operator on publishers (Future is a publisher) is like await. Here's an example, loosely based on your code:

    Future<Feature, Error> { promise in
      directions.calculate(options) { (waypoints, routes, error) in
         if let error = error {
           promise(.failure(error))
         }
    
         promise(.success(routes))
      }
     }
     .flatMap { routes in 
       // extract feature from routes here...
       feature
     }
     .receiveOn(DispatchQueue.main) // UI updates should run on the main queue
     .sink(receiveCompletion: { completion in
        // completion is either a .failure or it's a .success holding
        // the extracted feature; if the process above was successful, 
        // you can now add feature to the map
     }, receiveValue: { _ in })
     .store(in: &self.cancellables)
    

    Edit: I went into more detail in this blog post.

    0 讨论(0)
  • 2020-12-28 15:43

    You can use this framework for Swift coroutines - https://github.com/belozierov/SwiftCoroutine

    Unlike DispatchSemaphore, when you call await it doesn’t block the thread but only suspends coroutine, so you can use it in the main thread as well.

    func awaitAPICall(_ url: URL) throws -> String? {
        let future = URLSession.shared.dataTaskFuture(for: url)
        let data = try future.await().data
        return String(data: data, encoding: .utf8)
    }
    
    func load(url: URL) {
        DispatchQueue.main.startCoroutine {
            let result1 = try self.awaitAPICall(url)
            let result2 = try self.awaitAPICall2(result1)
            let result3 = try self.awaitAPICall3(result2)
            print(result3)
        }
    }
    
    0 讨论(0)
  • 2020-12-28 15:47

    (Note: Swift 5 may support await as you’d expect it in ES6!)

    What you want to look into is Swift's concept of "closures". These were previously known as "blocks" in Objective-C, or completion handlers.

    Where the similarity in JavaScript and Swift come into play, is that both allow you to pass a "callback" function to another function, and have it execute when the long-running operation is complete. For example, this in Swift:

    func longRunningOp(searchString: String, completion: (result: String) -> Void) {
        // call the completion handler/callback function
        completion(searchOp.result)
    }
    longRunningOp(searchString) {(result: String) in
        // do something with result
    }        
    

    would look like this in JavaScript:

    var longRunningOp = function (searchString, callback) {
        // call the callback
        callback(err, result)
    }
    longRunningOp(searchString, function(err, result) {
        // Do something with the result
    })
    

    There's also a few libraries out there, notably a new one by Google that translates closures into promises: https://github.com/google/promises. These might give you a little closer parity with await and async.

    0 讨论(0)
  • 2020-12-28 15:57

    You can use semaphores to simulate async/await.

    func makeAPICall() -> Result <String?, NetworkError> {
                let path = "https://jsonplaceholder.typicode.com/todos/1"
                guard let url = URL(string: path) else {
                    return .failure(.url)
                }
                var result: Result <String?, NetworkError>!
                
                let semaphore = DispatchSemaphore(value: 0)
                URLSession.shared.dataTask(with: url) { (data, _, _) in
                    if let data = data {
                        result = .success(String(data: data, encoding: .utf8))
                    } else {
                        result = .failure(.server)
                    }
                    semaphore.signal()
                }.resume()
                _ = semaphore.wait(wallTimeout: .distantFuture)
                return result
     }
    

    And here is example how it works with consecutive API calls:

    func load() {
            DispatchQueue.global(qos: .utility).async {
               let result = self.makeAPICall()
                    .flatMap { self.anotherAPICall($0) }
                    .flatMap { self.andAnotherAPICall($0) }
                
                DispatchQueue.main.async {
                    switch result {
                    case let .success(data):
                        print(data)
                    case let .failure(error):
                        print(error)
                    }
                }
            }
        }
    

    Here is the article describing it in details.

    And you can also use promises with PromiseKit and similar libraries

    0 讨论(0)
  • 2020-12-28 16:00

    Thanks to vadian's comment, I found what I expected, and it's pretty easy. I use DispatchGroup(), group.enter(), group.leave() and group.notify(queue: .main){}.

    func myFunction() {
        let array = [Object]()
        let group = DispatchGroup() // initialize
    
        array.forEach { obj in
    
            // Here is an example of an asynchronous request which use a callback
            group.enter() // wait
            LogoRequest.init().downloadImage(url: obj.url) { (data) in
                if (data) {
                    group.leave() // continue the loop
                }
            }
        }
    
        group.notify(queue: .main) {
            // do something here when loop finished
        }
    }
    
    0 讨论(0)
提交回复
热议问题