Swift - 如何从一个单独的文件中的函数使用多线程

3
我试着在Swift中使用多线程来检索并在视图控制器中显示一张图片。但是,用于检索图片的函数位于单独的模型中。我可以将该函数复制粘贴到视图控制器中,但我将多次重复使用此函数,并且更喜欢将其与特定的视图控制器分开。
我目前拥有的函数(同样在我命名为“WikimediaAPI”的单独文件中)如下所示:
public func getThumbnailImage(forPage page: String) -> UIImage? {
        if let data = try? Data(contentsOf: URL(string: "https://en.wikipedia.org/w/api.php?action=query&titles=" + page + "&prop=pageimages&format=json&pithumbsize=1000")!) {
            let json = JSON(data: data)
            if let pageId = json["query"]["pages"].dictionary?.first?.key {
                if let imageUrl = json["query"]["pages"][pageId]["thumbnail"]["source"].string {
                    if let url = URL(string: imageUrl) {
                        if let imageData = try? Data(contentsOf: url) {
                            if let image = UIImage(data: imageData) {
                                return image
                            }
                        }
                    }
                }
            }
        }
        return nil
    }

上述代码可行,但是不支持多线程,当我从表视图控制器切换到此视图控制器时,时间太长。我尝试按照以下方式实现多线程,但是我无法在异步块中返回任何内容。
public func getThumbnailImage(forPage page: String) -> UIImage? {
        DispatchQueue.global().async {
            if let data = try? Data(contentsOf: URL(string: "https://en.wikipedia.org/w/api.php?action=query&titles=" + page + "&prop=pageimages&format=json&pithumbsize=1000")!) {
                DispatchQueue.main.async {
                    let json = JSON(data: data)
                    if let pageId = json["query"]["pages"].dictionary?.first?.key {
                        if let imageUrl = json["query"]["pages"][pageId]["thumbnail"]["source"].string {
                            if let url = URL(string: imageUrl) {
                                DispatchQueue.global().async {
                                    if let imageData = try? Data(contentsOf: url) {
                                        DispatchQueue.main.async {
                                            if let image = UIImage(data: imageData) {
                                                return image //error: "Unexpected non-void return value in void function"
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        return nil
    }

我曾经考虑过在异步代码块中返回闭包,而不是UIImage,但我不确定如何实现。如果这样做,我仍然需要在视图控制器中设置图像,这一点我也不确定该怎么做。
以下是在视图控制器中调用该函数的代码:
let wikiQuery = WikimediaAPI() // the class from above

fileprivate func setImage() {
        if let pageTitle = wikimediaPageTitleDict[(allergy.0).lowercased()] {
            if let image = wikiQuery.getThumbnailImage(forPage: pageTitle) {
                wikipediaImage.image = image
                wikipediaImage.contentMode = .scaleAspectFit
            }

        }

  }

我正在使用一个字典(wikimediaPageTitleDict)从plist文件中将字符串与正确的维基百科页面(allergy.0是该字符串)关联起来。
感谢任何帮助。谢谢!

另外,上面提到的JSON是指SwiftyJSON(https://github.com/SwiftyJSON/SwiftyJSON)。 - MattHusz
2
提醒一下,您可以将if let链接到同一条件中,以避免死亡金字塔。 - JAL
3个回答

1

无论是AlamofireImage还是SDWebImage,它们都会为您完成此操作,并且它们扩展了UIImageView,因此您只需在图像视图上调用一个方法,当图像被下载(或从缓存中检索)时,图像视图将得到更新。我建议您使用库来完成这个操作。但是,如果您想自己完成(请注意,使用guard语句可以消除if let嵌套的问题):

    typealias ImageCallback = (UIImage?) -> Void

    func getThumbnailImage(forPage page: String, completion: @escaping ImageCallback) -> void {
        DispatchQueue.global().async {
            var imageToReturn: UIImage? = nil
            defer {
                completion(imageToReturn)
            }
            guard let data = try? Data(contentsOf: URL(string: "https://en.wikipedia.org/w/api.php?action=query&titles=" + page + "&prop=pageimages&format=json&pithumbsize=1000")!) else {
                return
            }
            let json = JSON(data: data)
            guard let pageId = json["query"]["pages"].dictionary?.first?.key,
                  let imageUrl = json["query"]["pages"][pageId]["thumbnail"]["source"].string,
                  let url = URL(string: imageUrl) else {
                return
            }
            guard let imageData = try? Data(contentsOf: url),
                  let image = UIImage(data: imageData) else {
                return
            }
            imageToReturn = image
        }

    }

    getThumbnailImage(forPage: "Escherichia_coli") { image in
        DispatchQueue.main.async {
            imageView.image = image
        }
    }

好的,我忘记了Alamofire已经内置了异步图像加载。这绝对是你已经在使用的Alamofire中最简单和最好的答案。 - Duncan C
谢谢!我最终使用了Alamofire,但演示代码对我理解通常应该如何完成非常有帮助。 - MattHusz
也很欣赏这个守卫结构,之前没有意识到我可以这样做,但看起来更加简洁。 - MattHusz

0
如果您正在使用异步代码,那么您无法从函数中返回结果。这不是异步工作的方式。异步函数调用会将结果排队以便稍后获取。然后函数在结果可用之前返回。
通常您使用异步函数时需要传入一个完成块,在结果可用时调用该块。例如,您可以传入一个闭包,将新下载的图像安装到视图控制器中的适当图像视图中。
请参阅我在此线程上的答案。 在Swift中存储completionHandlers的值 我提供了一个带有完成处理程序的异步函数的工作示例。

0

在 dispatch async 闭包中,你不能返回任何东西。 但是,你可以将 UIImageView 变量传递给你的 getThumbnailImage 方法, 当你接收到图像时,你可以立即设置它。

public func getThumbnailImage(forPage page: String, imageView : UIImageView) 
{
  ...
  ...
         if let imageData = try? Data(contentsOf: url) {
            DispatchQueue.main.async {
              if let image = UIImage(data: imageData) {
                imageView.image = image                                 
              }
            }
         }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接