简体   繁体   English

将 Alamofire 完成处理程序转换为异步/等待 | Swift 5.5, *

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

I have the current function which works.我有当前可用的 function。 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?如何将其转换为 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:下面的答案有效,但我在 Alamofire 团队实现异步时找到了自己的解决方案:

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
}

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.只需制作一个包含在延续块中的AF.request版本。 For example, the JSON decoding is not something that should be part of what's being wrapped;例如,JSON 解码不应该是被包装内容的一部分; 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.它是 .networking 的结果返回给你之后发生的事情——这就是你想要将AF.request转换为async function 的原因。

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:因此,例如,我要做的只是将AF.request最小化包装在async throws function 中,如果我们获得数据,我们将返回它,如果我们收到错误,我们将其抛出:

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.您会注意到我不需要解析泛型continuation类型,因为我已经声明了函数的返回类型。 (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.好的,重点是,现在在异步/等待世界中调用 function 是微不足道的。 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.最后我要补充一点,所有这些努力可能都白费了,因为我希望 Alamofire 的人现在随时都能使用他们自己的所有异步方法的async版本。

Personally I think swallowing errors inside a.network call is a bad idea, the UI should receive all errors and make the choice accordingly.我个人认为在 a.network 调用中吞下错误是个坏主意,UI 应该接收所有错误并做出相应的选择。

Here is an example of short wrapper around responseDecodable, that produces an async response.这是一个围绕 responseDecodable 的短包装器的示例,它产生一个异步响应。

public extension DataRequest {

    @discardableResult
    func asyncDecodable<T: Decodable>(of type: T.Type = T.self,
                                      queue: DispatchQueue = .main,
                                      dataPreprocessor: DataPreprocessor = DecodableResponseSerializer<T>.defaultDataPreprocessor,
                                      decoder: DataDecoder = JSONDecoder(),
                                      emptyResponseCodes: Set<Int> = DecodableResponseSerializer<T>.defaultEmptyResponseCodes,
                                      emptyRequestMethods: Set<HTTPMethod> = DecodableResponseSerializer<T>.defaultEmptyRequestMethods) async throws -> T {

        return try await withCheckedThrowingContinuation({ continuation in

            self.responseDecodable(of: type, queue: queue, dataPreprocessor: dataPreprocessor, decoder: decoder, emptyResponseCodes: emptyResponseCodes, emptyRequestMethods: emptyRequestMethods) { response in

                switch response.result {
                case .success(let decodedResponse):
                    continuation.resume(returning: decodedResponse)
                case .failure(let error):
                    continuation.resume(throwing: error)
                }
            }
        })
    }
}

This is a mix between my Answer and the one that matt provided.这是我的答案和马特提供的答案的混合体。 There will probably be an easier and cleaner implementation once the Alamofire team implements async but at least for now I'm out of the call backs hell...一旦 Alamofire 团队实现了异步,可能会有一个更简单、更清晰的实现,但至少现在我已经摆脱了回调地狱……

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("Error while doing Alamofire url request")
        }
    }
}


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

    do {
        let undecodedData = try await self.afRequest(url: url!)
        let decodedData = try JSONDecoder().decode(AllChainsClassAModel.self, from: undecodedData)
        allChains = decodedData.data.items
    } catch {
        print(error)
    }

    return allChains
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM