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()
}
}
请注意,您确实应该同步图像数组的更新(可变数组不是线程安全的)。
--
满怀希望地回答了这个问题,我必须承认我可能会建议两个进一步但重要的变化:
您不应该将图像保存在一个数组中(因为如果有太多,您可能 运行 内存不足)。 JPEG 或 PNG 文件可能看起来不太大,但当图像在 RAM 中解压缩时,它们会占用大量内存,您很容易收到内存警告。
您可能会考虑将图像下载到持久存储中,而不是内存中。如果你想在内存中保存图像,出于性能原因使用 NSCache
(但在内存警告时清除它)。
如果您没有太多图片,或者它们都很小,这可能并不重要。但是,如果您正在寻找可扩展的解决方案,则不需要您的应用程序将所有图像保存在一个数组中。
您可能不希望在重新加载集合视图之前等待所有图像。通常人们会建议延迟加载。因此,如果您有 1000 张图像,但一次只能看到 20 张,为什么要等待 1000 张图像呢?专注于加载可见细胞的图像。此外,您可能希望看到图像在下载时弹出到集合视图中(因此与其等待前 20 张可见图像下载完毕,不如在它们进入时显示它们)。
您通常通过 cellForItemAtIndexPath
异步启动图像下载并在完成后更新单元格来完成此操作。
我的应用程序有一个 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()
}
}
请注意,您确实应该同步图像数组的更新(可变数组不是线程安全的)。
--
满怀希望地回答了这个问题,我必须承认我可能会建议两个进一步但重要的变化:
您不应该将图像保存在一个数组中(因为如果有太多,您可能 运行 内存不足)。 JPEG 或 PNG 文件可能看起来不太大,但当图像在 RAM 中解压缩时,它们会占用大量内存,您很容易收到内存警告。
您可能会考虑将图像下载到持久存储中,而不是内存中。如果你想在内存中保存图像,出于性能原因使用
NSCache
(但在内存警告时清除它)。如果您没有太多图片,或者它们都很小,这可能并不重要。但是,如果您正在寻找可扩展的解决方案,则不需要您的应用程序将所有图像保存在一个数组中。
您可能不希望在重新加载集合视图之前等待所有图像。通常人们会建议延迟加载。因此,如果您有 1000 张图像,但一次只能看到 20 张,为什么要等待 1000 张图像呢?专注于加载可见细胞的图像。此外,您可能希望看到图像在下载时弹出到集合视图中(因此与其等待前 20 张可见图像下载完毕,不如在它们进入时显示它们)。
您通常通过
cellForItemAtIndexPath
异步启动图像下载并在完成后更新单元格来完成此操作。