如何使用 Swift 完成处理程序的结果?
Posted
技术标签:
【中文标题】如何使用 Swift 完成处理程序的结果?【英文标题】:How to use results from Swift completion handler? 【发布时间】:2020-11-06 02:08:00 【问题描述】:我是 Swift 和 SwiftUI 的新手。
在我的 macOS SwiftUI 项目中,我正在尝试验证 URL 是否可访问,以便我可以有条件地呈现两个视图之一。一个视图加载图像 URL,另一个视图在 URL 不可访问时显示错误图像。
这是我的 URL 扩展名:
import Foundation
extension URL
func isReachable(completion: @escaping (Bool) -> Void)
var request = URLRequest(url: self)
request.httpMethod = "HEAD"
request.timeoutInterval = 1.0
URLSession.shared.dataTask(with: request) data, response, error in
if error != nil
DispatchQueue.main.async
completion(false)
return
if let httpResp: HTTPURLResponse = response as? HTTPURLResponse
DispatchQueue.main.async
completion(httpResp.statusCode == 200)
return
else
DispatchQueue.main.async
completion(false)
return
.resume()
在其他地方,我正在尝试在模型视图中使用它:
var imageURL: URL?
if let url = self.book.image_url
return URL(string: url)
else
return nil
var imageURLIsReachable: Bool
if let url = self.imageURL
url.isReachable result in
return result // Error: Cannot convert value of type 'Bool' to closure result type 'Void'
else
return false
虽然 Xcode 显示此错误:
Cannot convert value of type 'Bool' to closure result type 'Void'
我做错了什么?
【问题讨论】:
您不能通过异步获得的计算属性同步返回值。在您的完成处理程序中,您可以将结果分配给一个属性,并使用另一种机制(didSet
或通过@Published
)对更改采取行动。
programmingios.net/returning-a-value-from-asynchronous-code - 您不能返回依赖于 url.isReachable
的 Bool,原因与 url.isReachable
使用完成处理程序的原因相同:它是 异步的。这意味着它会在未来发生。人们总是说“我是 Swift 的新手”等等,但肯定有人认为你不能现在做一些取决于未来会发生什么的事情的想法并不陌生.
与您的问题无关,但 1 秒超时时间太短,无法定义远程 URL 是否可访问。 URLRequest 超时的默认值为 1 分钟
@LeoDabus 我只是在做一个HEAD
请求。你认为我需要超过 1 秒吗? developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD
我不知道,但不是每个人都有良好的联系。安全总比后悔好。顺便说一句,超时与完成下载所需的时间无关
【参考方案1】:
在阅读了这里的一些 cmets 并进行了更多的研究/实验后,我得到了这个工作。这是我所做的更改:
在 URL 扩展中,我将其保留为几乎相同,因为我发现这种方式更具可读性。我确实将timeoutInterval
推送到了一个参数:
// Extensions/URL.swift
import Foundation
extension URL
func isReachable(timeoutInterval: Double, completion: @escaping (Bool) -> Void)
var request = URLRequest(url: self)
request.httpMethod = "HEAD"
request.timeoutInterval = timeoutInterval
URLSession.shared.dataTask(with: request) data, response, error in
if error != nil
DispatchQueue.main.async
completion(false)
return
if let httpResp: HTTPURLResponse = response as? HTTPURLResponse
DispatchQueue.main.async
completion(httpResp.statusCode == 200)
return
else
DispatchQueue.main.async
completion(false)
return
.resume()
我修改了我的BookViewModel
,将其中两个属性设置为@Published
,并在那里使用了网址扩展:
// View Models/BookViewModel.swift
import Foundation
class BookViewModel: ObservableObject
@Published var book: Book
@Published var imageURLIsReachable: Bool
@Published var imageURL: URL?
init(book: Book)
self.book = book
self.imageURL = nil
self.imageURLIsReachable = false
if let url = book.image_url
self.imageURL = URL(string: url)
self.imageURL!.isReachable(timeoutInterval: 1.0) result in
self.imageURLIsReachable = result
// Rest of properties...
现在我的BookThumbnailView
可以正常显示条件视图了:
// Views/BookThumbnailView.swift
import SwiftUI
import Foundation
import KingfisherSwiftUI
struct BookThumbnailView: View
@ObservedObject var viewModel: BookViewModel
private var book: Book
viewModel.book
@ViewBuilder
var body: some View
if let imageURL = self.viewModel.imageURL
if self.viewModel.imageURLIsReachable
KFImage(imageURL)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(maxWidth: 70)
.cornerRadius(8)
else
ErrorBookThumbnailView()
else
DefaultBookThumbnailView()
哇,那真是一次学习经历。感谢所有提出建议并提供建议的人!
【讨论】:
【参考方案2】:正如 Xcode 告诉你的那样,问题确实存在于return result
行。当您创建函数func isReachable(completion: @escaping (Bool) -> Void)
时,您是在告诉Xcode 您将输入(Bool) -> Void
类型的内容,应该类似于func someFunction(input: Bool) -> Void
。
但是当您使用闭包输入完成处理程序时,您输入的是Bool -> Bool
类型的函数。删除return result
行,或更改func isReachable(completion:)
中的补全类型。
编辑:
确实,我不建议在计算属性中返回异步结果,否则会导致其他问题。
我会把它改成这样的:
func isReachable(completion: @esacping (Bool) -> Void)
...
func showResultView()
guard let url = imageURL else
// handling if the imageURL is nil
return
url.isReachable result in
// do something with the result
if result
// show viewController A
else
// show viewController B
// call showResultView anywhere you want, lets say you want to show it whenever the viewController appear
override func viewDidAppear()
...
showResultView()
【讨论】:
我尝试在视图正文中使用该代码,但似乎不允许这样做。你会把那个代码放在哪里?另外,到目前为止,我还没有任何 ViewController;我主要使用MVVM。但我对解决方案持开放态度,以使其发挥作用。谢谢 对不起,我没有说得足够清楚,代码不是直接在 View body 中调用的。请查看我编辑的答案。 在我的理解中,MVVM专注于将View(UI)和Model(数据)分开,没有任何ViewController不是必须的,所以我有点困惑“我不'没有任何 ViewControllers;我主要使用 MVVM。”,确实在我使用 MVVM 的项目中确实有 viewController、viewModel、Model 和一些 View 以供重用。但无论如何,请在视图的生命周期中调用它,或者在需要时调用它,但不要直接将其粘贴到视图的类主体上。希望能把事情弄清楚一点。 我在某处读到“SwiftUI 将 UIView 和 UIViewController 混合到一个 View 协议中,这使得我们的代码更加简单。”。所以,我会尝试将该代码添加到我的相关视图中。再次感谢!以上是关于如何使用 Swift 完成处理程序的结果?的主要内容,如果未能解决你的问题,请参考以下文章