将 Alamofire 完成处理程序转换为 Async/Await |斯威夫特 5.5,*
Posted
技术标签:
【中文标题】将 Alamofire 完成处理程序转换为 Async/Await |斯威夫特 5.5,*【英文标题】:Convert Alamofire Completion handler to Async/Await | Swift 5.5, * 【发布时间】:2021-08-07 18:03:06 【问题描述】:我有当前可用的功能。我将它与完成处理程序一起使用:
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)
)
如何将其转换为 swift 5.5 的新 async/await 功能?
这是我尝试过的:
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
但我没有得到 print 语句末尾的值。
我有点迷失在这个过程中,我想转换我的旧函数,这个例子会有很大帮助。
编辑:
下面的答案有效,但我在 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
【问题讨论】:
Alamofire 已开始在feature/async-handlers
分支上进行并发工作。您可以使用该分支或将包装器工作复制到您自己的代码中。
我会等到正式发布 @JonShier 感谢您的提醒,我很期待 :)
【参考方案1】:
首先,你的结构是错误的。不要从您的原始代码开始并将其 all 包装在延续块中。只需制作一个包含在延续块中的 AF.request
本身的版本。例如,JSON 解码不应该是被包装内容的一部分;它是网络结果返回给您之后的结果——这就是您想要将AF.request
转换为async
函数的原因。
其次,正如错误消息告诉你的那样,解析泛型,要么返回显式返回类型,要么将类型声明为延续声明的一部分。
因此,例如,我要做的只是将AF.request
包装在一个async throws
函数中,如果我们得到数据,我们会返回它,如果我们得到错误,我们会抛出它:
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")
你会注意到我不需要解析通用的continuation
类型,因为我已经声明了函数的返回类型。 (这就是为什么我在我的online tutorial 中向您指出我对这个主题的解释和示例;您阅读了吗?)
好的,重点是,现在在 async/await 世界中调用该函数是微不足道的。一个可能的基本结构是:
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
最后我要补充一点,无论如何,所有这些努力都可能是白费了,因为我希望 Alamofire 人随时都可以使用他们自己的所有异步方法的 async
版本。
【讨论】:
我明白你在说什么,现在它以某种方式工作,但在函数结束时返回值仍然存在问题,哦,我的头被炸了......从今天早上开始,我认为我很接近 我需要在JSONDecoder
上再等待一次吗?因为该值在 try catch 中并且超出了返回它的范围
我更新了底部问题的代码,为什么print语句没有得到数据?
任务到底是干什么用的?我刚刚告诉过你,解码器不是异步的。
我在我的答案中添加了一个可能的实现架构。【参考方案2】:
我个人认为在网络调用中吞下错误是一个坏主意,UI 应该接收所有错误并做出相应的选择。
以下是 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)
)
【讨论】:
【参考方案3】:这是我的答案和马特提供的答案之间的混合。一旦 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
【讨论】:
其实和我说的基本一样。而且也不是那么好,因为出错时返回一个空数组是没有意义的。 那我该如何退货呢?不包括在您的答案中(为简单起见,我没有提及)@matt 发出失败信号的方法就是失败。你看过我的回答吗? (好像没有,就像你没有阅读我给你的链接一样。)“如果你声明这个方法为抛出,你可以重新抛出[错误]。)”以上是关于将 Alamofire 完成处理程序转换为 Async/Await |斯威夫特 5.5,*的主要内容,如果未能解决你的问题,请参考以下文章
将带有完成处理程序的 Firebase IdToken 添加到每个 AlamoFire 请求
Alamofire 请求最初返回值,但在通过完成处理程序时被接收为 nil