Swift中GCD预下载UIImage至数据结构出现0x0图片问题求助
预下载图片并存储供TableView使用的正确姿势
你的问题核心在于异步任务的执行时机——你在循环里启动了图片下载的异步线程,但还没等图片下载完成,就已经把初始的空UIImage()赋值给News对象了,所以最终每个newsArticle的thumbnail都是尺寸为{0,0}的空图。异步操作不会阻塞主线程/循环,循环会继续往下走,完全不等下载任务结束。
下面给你两种可行的解决方案,按需选择:
方案一:用DispatchGroup跟踪所有下载,完成后统一刷新TableView
适合图片数量不多的场景,能确保所有图片下载完成后再展示:
1. 先调整你的News模型
把thumbnail改成可选类型,因为初始时图片还没下载:
struct News { let title: String let link: String var thumbnail: UIImage? // 可选类型,初始为nil }
2. 预下载并更新模型
var news = [News]() let downloadGroup = DispatchGroup() for item in parser.parsedData { // 先安全解析数据,避免强制解包崩溃 guard let currentDict = item as? [String: String], let title = currentDict["title"], let link = currentDict["link"], let urlString = currentDict["media:content"], let imageURL = URL(string: urlString) else { continue } // 创建初始的News对象,thumbnail暂时为nil let newsItem = News(title: title, link: link, thumbnail: nil) news.append(newsItem) let currentIndex = news.count - 1 // 记录当前对象在数组中的索引 // 标记任务开始 downloadGroup.enter() DispatchQueue.global().async { // 下载图片(实际项目建议用try-catch处理错误,这里简化) if let imageData = try? Data(contentsOf: imageURL), let downloadedImage = UIImage(data: imageData) { DispatchQueue.main.async { // 回到主线程更新模型 news[currentIndex].thumbnail = downloadedImage downloadGroup.leave() // 标记任务完成 } } else { // 下载失败也要标记完成,避免group永远等待 downloadGroup.leave() } } } // 所有下载完成后,刷新TableView downloadGroup.notify(queue: .main) { self.tableView.reloadData() }
3. 在cellForRowAt中使用
直接从模型取图片,没有的话用占位图:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "YourCellID", for: indexPath) as! YourCustomCell let newsItem = news[indexPath.row] cell.titleLabel.text = newsItem.title // 用占位图替代空图,提升用户体验 cell.thumbnailImageView.image = newsItem.thumbnail ?? UIImage(named: "placeholder_image") return cell }
方案二:用NSCache做图片缓存(更推荐)
如果图片数量较多,或者担心内存占用,用NSCache是更优的选择——既能预下载缓存图片,后续滚动TableView时也能直接从缓存取,不用重复下载,还能自动管理内存:
1. 调整News模型,存储图片URL而不是图片
struct News { let title: String let link: String let imageURL: String // 存储图片URL,后续从缓存取 }
2. 创建全局缓存对象
在你的ViewController里定义:
let imageCache = NSCache<NSString, UIImage>()
3. 预下载并缓存图片
var news = [News]() for item in parser.parsedData { guard let currentDict = item as? [String: String], let title = currentDict["title"], let link = currentDict["link"], let urlString = currentDict["media:content"], let imageURL = URL(string: urlString) else { continue } // 先创建News对象并存入数组 let newsItem = News(title: title, link: link, imageURL: urlString) news.append(newsItem) // 异步下载并缓存图片 DispatchQueue.global().async { if let imageData = try? Data(contentsOf: imageURL), let downloadedImage = UIImage(data: imageData) { // 存入缓存,key用urlString self.imageCache.setObject(downloadedImage, forKey: urlString as NSString) // 可选:如果想实时更新已下载的行,刷新对应indexPath DispatchQueue.main.async { if let rowIndex = self.parser.parsedData.firstIndex(where: { ($0 as? [String: String])?["media:content"] == urlString }) { self.tableView.reloadRows(at: [IndexPath(row: rowIndex, section: 0)], with: .fade) } } } } }
4. 在cellForRowAt中优先取缓存
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "YourCellID", for: indexPath) as! YourCustomCell let newsItem = news[indexPath.row] cell.titleLabel.text = newsItem.title // 先从缓存取图片 if let cachedImage = imageCache.object(forKey: newsItem.imageURL as NSString) { cell.thumbnailImageView.image = cachedImage } else { // 缓存没有,作为降级方案再次下载(比如预下载失败的情况) guard let url = URL(string: newsItem.imageURL) else { cell.thumbnailImageView.image = UIImage(named: "placeholder_image") return cell } DispatchQueue.global().async { if let imageData = try? Data(contentsOf: url), let downloadedImage = UIImage(data: imageData) { self.imageCache.setObject(downloadedImage, forKey: newsItem.imageURL as NSString) DispatchQueue.main.async { cell.thumbnailImageView.image = downloadedImage } } } } return cell }
额外提示
- 实际项目中,不要用
try? Data(contentsOf: url)这种简单下载方式,建议用URLSession的任务,能更好地处理错误、取消任务(比如TableView滚动时取消不在屏幕上的下载任务)。 - 占位图能极大提升用户体验,避免滚动时出现空白。
内容的提问来源于stack exchange,提问作者Mitchell D
相关产品推荐
相关产品推荐

