简体   繁体   中英

How to use results from Swift completion handler?

I'm new to Swift and SwiftUI.

In my macOS SwiftUI project, I'm trying to verify that a URL is reachable so I can present one of two views conditionally. One view which loads the image URL, another one which displays an error image if the URL is not reachable.

Here's my URL extension with completion:

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()
    }
}

Elsewhere, I'm trying to use that in a model-view:

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
    }
}

Though Xcode is showing this error:

Cannot convert value of type 'Bool' to closure result type 'Void'

What am I doing wrong?

I got this to work after reading some of the comments here and doing more research/experimentation. Here's what I changed:

In the URL extension, I left it pretty much the same as I find it more readable this way. I did push the timeoutInterval to a parameter:

// 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()
    }
}

I modified my BookViewModel to make two of the properties to @Published and used the URL extension there:

// 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...
}

Now my BookThumbnailView can properly display the conditional views:

// 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()
        }
    }
}

Whew, that was quite the learning experience. Thanks to everyone who commented with suggestions and provided hints on where to look!

The problem literally laid in the line return result , as Xcode tells you. When you create your function func isReachable(completion: @escaping (Bool) -> Void) , you are telling Xcode that you are going to input something in the type of (Bool) -> Void , which should be something like func someFunction(input: Bool) -> Void .

But when you use a closure to input the completion handler, you're inputting a function in type Bool -> Bool . Remove the line return result , or change the type of the completion in your func isReachable(completion:) .

Edit:

And indeed I don't recommend returning a async result in a computed property, that would cause some other problem.

I would change it to something like:

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()
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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