Search code examples
swiftswiftuialamofirealamofire-requestalamofire5

Convert Alamofire Completion handler to Async/Await | Swift 5.5, *


I have the current function which works. I'm using it with completion handler:

func getTokenBalances(completion: @escaping (Bool) -> Void) {
    guard let url = URL(string: "someApiUrlFromLostandFound") else {
        print("Invalid URL")
        completion(false)
        return
    }
    
    AF.request(url, method: .get).validate().responseData(completionHandler: { data in
        do {
            guard let data = data.data else {
                print("Response Error:", data.error as Any)
                completion(false)
                return
            }
            
            let apiJsonData = try JSONDecoder().decode(TokenBalanceClassAModel.self, from: data)
            DispatchQueue.main.async {
                self.getTokenBalancesModel = apiJsonData.data.items
                completion(true)
            }
        } catch {
            print("ERROR:", error)
            completion(false)
        }
    })
}

How can I convert it to the new async/await functionality of swift 5.5?

This is what I've tried:

func getTokenBalances3() async {
    let url = URL(string: "someApiUrlFromLostandFound")

    let apiRequest = await withCheckedContinuation { continuation in
        AF.request(url!, method: .get).validate().responseData { apiRequest in
            continuation.resume(returning: apiRequest)
        }
    }
    
    
    let task1 = Task {
        do {
            // Decoder is not asynchronous
            let apiJsonData = try JSONDecoder().decode(SupportedChainsClassAModel.self, from: apiRequest.data!)
//            Working data ->    print(String(apiJsonData.data.items[0].chain_id!))
        } catch {
            print("ERROR:", error)
        }
    }
        
    let result1 = await task1.value
    
    print(result1)  // values are not printed
}

But I'm not getting the value at the end on the print statement.

I'm kind of lost in the process, I'd like to convert my old functions, with this example it would help a lot.

EDIT:

The Answer below works, but I found my own solution while the Alamofire team implements async:

func getSupportedChains() async throws -> [AllChainsItemsClassAModel] {
    var allChains: [AllChainsItemsClassAModel] = [AllChainsItemsClassAModel]()
    let url = URL(string: covalentHqUrlConnectionsClassA.getCovalenHqAllChainsUrl())

    let apiRequest = await withCheckedContinuation { continuation in
        AF.request(url!, method: .get).validate().responseData { apiRequest in
            continuation.resume(returning: apiRequest)
        }
    }

    do {
        let data = try JSONDecoder().decode(AllChainsClassAModel.self, from: apiRequest.data!)
        allChains = data.data.items
    } catch {
        print("error")
    }

    return allChains
}

Solution

  • First of all, your structure is wrong. Do not start with your original code and wrap all of it in the continuation block. Just make a version of AF.request itself that's wrapped in a continuation block. For example, the JSON decoding is not something that should be part of what's being wrapped; it is what comes after the result of networking returns to you — it is the reason why you want to turn AF.request into an async function to begin with.

    Second, as the error message tells you, resolve the generic, either by the returning into an explicit return type, or by stating the type as part of the continuation declaration.

    So, for example, what I would do is just minimally wrap AF.request in an async throws function, where if we get the data we return it and if we get an error we throw it:

    func afRequest(url:URL) async throws -> Data {
        try await withUnsafeThrowingContinuation { continuation in
            AF.request(url, method: .get).validate().responseData { response in
                if let data = response.data {
                    continuation.resume(returning: data)
                    return
                }
                if let err = response.error {
                    continuation.resume(throwing: err)
                    return
                }
                fatalError("should not get here")
            }
        }
    }
    

    You'll notice that I didn't need to resolve the generic continuation type because I've declared the function's return type. (This is why I pointed you to my explanation and example in my online tutorial on this topic; did you read it?)

    Okay, so the point is, now it is trivial to call that function within the async/await world. A possible basic structure is:

    func getTokenBalances3() async {
        let url = // ...
        do {
            let data = try await self.afRequest(url:url)
            print(data)
            // we've got data! okay, so
            // do something with the data, like decode it
            // if you declare this method as returning the decoded value,
            // you could return it
        } catch {
            print(error)
            // we've got an error! okay, so
            // do something with the error, like print it
            // if you declare this method as throwing,
            // you could rethrow it
        }
    }
    

    Finally I should add that all of this effort is probably wasted anyway, because I would expect the Alamofire people to be along with their own async versions of all their asynchronous methods, any time now.