从 Swift 中的 URL 列表下载内容的安全方法 3

Safe way to download content from a list of URL's in Swift 3

我有一个 URL 的列表,它提供了图像资源的位置。我设法找到了一个解决方案,但我觉得好像有比下面代码中显示的过程更好的方法。非常感谢有关如何改进此方法以异步检索图像的任何提示!

在我附加每个项目之后调用 completionHandler 并在异步块之外将 1 添加到索引 (i) 并不奇怪,这意味着 while 循环迭代到之前的下一个 url 项目最后的url已经处理完了??

typealias imagesHandler = (_ images: [UIImage]) -> Void

func fetchImages(forUser user: User, completionHandler: imagesHandler?) {

    var images = [UIImage]()

    self.fetchImageUrlList(forUser: user) { (urlList: [URL]) in

        var i = 0

        while i <= urlList.count - 1 {
            let request = URLRequest(url: urlList[i])
            let dataTask = URLSession(configuration: .default).dataTask(with: request, completionHandler: {
                (data: Data?, response: URLResponse?, error: Error?) in
                guard error == nil else { return }
                guard let data = data else { return }
                guard let image = UIImage(data: data) else { return }

                images.append(image)
                completionHandler?(Array(Set(images)))
            })
            i += 1
            dataTask.resume()
        }
    }
}

如果你想同时发出所有三个请求(这很好),那么你将按原样递增 i(因为这是在 urlList 数组中循环)。

您可能遇到的问题是完成时的线程争用。附加到图像数组不是原子操作,我不知道这些完成是否都发生在同一个线程上。您可以使用只有一个线程为其提供服务的委托队列构建 URLSession —— 这将确保在协调的情况下完成。如果没有,您可能需要将更新同步到 images

此外,您正在为每个完成的图像调用一次 fetchImages completionHandler(不确定您是否想要),而不是在它们全部完成时调用一次。

大中央派遣

这是一个典型的多线程场景,Grand Central Dispatch 非常有用。

首先,为了使用 Grand Central Dispatch,请更新您的代码

func fetchImages(forUser user: User, completionHandler: ImagesHandler?) {

    var images = [UIImage]()
    let group = DispatchGroup()
    let serialQueue = DispatchQueue(label: "serialQueue")

    fetchImageUrlList(forUser: user) { urls in

        urls.forEach { url in
            // ***********************************************
            // tells the group that there is a pending process
            group.enter()
            URLSession(configuration: .default).dataTask(with: url) { (data, response, error) in

                guard let data = data, let image = UIImage(data: data), error == nil else { group.leave(); return }

                // ***************************************************************************
                // creates a synchronized access to the images array
                serialQueue.async {
                    images.append(image)

                    // ****************************************************
                    // tells the group a pending process has been completed
                    group.leave()
                }
            }.resume()
        }

        group.notify(queue: .main) {
            // *****************************************************************************************
            // this will be executed when for each group.enter() call, a group.leave() has been executed
            completionHandler?(images)
        }
    }
}

群发

我用这一行创建了一个 GroupDispatch

let group = DispatchGroup()

你可以把它看成一个计数器。每次调用 group.enter() 时,计数器都会增加 1。每次调用 group.leave() 时,计数器都会减少 1

当然 DispatchGroup线程安全的

最后你写的一切

group.notify(queue: .main) {
    // HERE <------
}

会在DispatchGroup的内部计数器为zero时执行。事实上,这意味着所有待处理的 "operations" 都已完成。

串行队列

最后,您需要从不同的线程访问 images 数组。这是非常危险的。

所以我们使用串行队列。

serialQueue.async {
    images.append(image)
    group.leave()
}

我们附加到一个Serial Queue的所有闭包都被执行了1次。所以 images 数组是安全访问的。

I haven't tested the code, please try and let me know ;)