You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 06:41:18