如何使用 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 完成处理程序的结果?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 swift 中包含多个功能的完成处理程序?

如何在 Swift 中使用完成处理程序链接函数?

如何在 API 调用中设置完成处理程序 - Swift 5

Swift 3 如何正确编写完成处理程序块

如何在 Swift 中创建一个我可以选择调用的完成处理程序?

如何在 Swift 的完成处理程序中返回布尔值