For 循环中的 NSURLSession 下载图像数据然后添加到 NSMutableArray

NSURLSession in For Loop to Download Image Data then add to NSMutableArray

我的应用程序有一个 UITableView,其单元格包含 UICollectionView 子视图(未重用)。 collectionView 的单元格有一个 UIImageView 来保存图片。我试图一次下载所有图像,将它们存储在一个数组中,然后在图像全部下载完成后 在 collectionView 单元格中显示数组的内容。在 ObjC 中,我已经使用 NSURLConnection 做了很多次,但我正在尝试使用 Swift 和 NSURLConnection 来实现这一点,但我 运行 我认为可能是并发或语义问题。也许你们中的某个人可以指出我正确的方向,因为我觉得我在这上面花了太多时间。

在我的 UITableView 的包含我正在调用的 collectionView 的单元格子类中

func downloadPhotosWithURLs(urls: [String]) {

    var imagesMutableArray = NSMutableArray()

    for url in urls {

        NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: url)!, completionHandler: { (data, repsonse, err) -> Void in

            if let imgData = data {

                if let image = UIImage(data: imgData) {

                    imagesMutableArray.addObject(image)

                }

                else {

                    // PUT IN "Photo Unavailable" Pic
                }
            }

            else {

                // PUT IN "Photo Unavailable" Pic
            }

        }).resume()

    }

    // UPDATE (but async op not done)
    if let downloadedImages: [UIImage] = imagesMutableArray as NSArray as? [UIImage] {

        self.images = downloadedImages

        dispatch_async(dispatch_get_main_queue(), { () -> Void in

            self.collectionView.reloadData()
        })

    }

}

问题是当我要求重新加载 collectionView 时,图像尚未获取。 for 循环中的异步操作肯定是罪魁祸首。如何在 图片全部下载后 调用我的 // UPDATE 代码?

如果你想等待一系列异步任务完成,创建一个dispatch_group_t,在开始每个请求之前进入组,在完成块中离开组,然后你可以依赖dispatch_group_notify 当每个 "enter" 与 "leave"

匹配时调用
func downloadPhotosWithURLs(urls: [String]) {
    var images = [UIImage]()

    let group = dispatch_group_create()

    for url in urls {
        dispatch_group_enter(group)
        NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: url)!, completionHandler: { data, response, error in

            if let imgData = data {
                if let image = UIImage(data: imgData) {
                    dispatch_sync(dispatch_get_main_queue()) {
                        images.append(image)
                    }
                } else {
                    // PUT IN "Photo Unavailable" Pic
                }
            } else {
                // PUT IN "Photo Unavailable" Pic
            }

            dispatch_group_leave(group)
        }).resume()
    }

    // UPDATE
    dispatch_group_notify(group, dispatch_get_main_queue()) {
        self.images = images
        self.collectionView.reloadData()
    }
}

请注意,您确实应该同步图像数组的更新(可变数组不是线程安全的)。

--

满怀希望地回答了这个问题,我必须承认我可能会建议两个进一步但重要的变化:

  1. 您不应该将图像保存在一个数组中(因为如果有太多,您可能 运行 内存不足)。 JPEG 或 PNG 文件可能看起来不太大,但当图像在 RAM 中解压缩时,它们会占用大量内存,您很容易收到内存警告。

    您可能会考虑将图像下载到持久存储中,而不是内存中。如果你想在内存中保存图像,出于性能原因使用 NSCache(但在内存警告时清除它)。

    如果您没有太多图片,或者它们都很小,这可能并不重要。但是,如果您正在寻找可扩展的解决方案,则不需要您的应用程序将所有图像保存在一个数组中。

  2. 您可能不希望在重新加载集合视图之前等待所有图像。通常人们会建议延迟加载。因此,如果您有 1000 张图像,但一次只能看到 20 张,为什么要等待 1000 张图像呢?专注于加载可见细胞的图像。此外,您可能希望看到图像在下载时弹出到集合视图中(因此与其等待前 20 张可见图像下载完毕,不如在它们进入时显示它们)。

    您通常通过 cellForItemAtIndexPath 异步启动图像下载并在完成后更新单元格来完成此操作。