TableView Cell加载图片轻微卡顿,Swift异步如何正确使用?
解决TableView加载内存中Data图片时的滚动卡顿问题
嘿,我完全懂你遇到的这个糟心问题——明明图片Data已经在内存里了,滚动到对应Cell时还是会出现画面抖动,而且Data越大卡顿越明显。核心原因其实是把Data转成UIImage的解码操作默认是在主线程执行的,这是个CPU密集型任务,会直接阻塞主线程的UI刷新,导致滚动不流畅。下面给你一套亲测有效的解决方案:
核心思路:异步解码+缓存已解码图片
1. 用NSCache缓存解码后的图片
先准备一个专门的缓存容器,NSCache比普通字典更适合做这件事——它会在内存不足时自动清理缓存,避免内存过载:
import UIKit // 可以放在类内部或者全局,根据你的代码结构调整 let imageCache = NSCache<NSString, UIImage>() struct messageCellStruct { let message: String! let photoData: Data! } var messageArray = [messageCellStruct]()
2. 在Cell配置时异步解码图片
在tableView(_:cellForRowAt:)方法里,把图片解码操作放到后台队列执行,完成后再回到主线程更新UI,同时缓存结果避免重复解码:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "MessageCell", for: indexPath) as! MessageCell let item = messageArray[indexPath.row] // 先设置文本内容 cell.messageLabel.text = item.message // 生成唯一的缓存Key(这里用索引,你也可以用图片Data的hash值,更稳妥) let cacheKey = NSString(string: "\(indexPath.row)_image") // 先检查缓存,有现成的解码好的图片就直接用 if let cachedImage = imageCache.object(forKey: cacheKey) { cell.photoImageView.image = cachedImage return cell } // 缓存里没有,就放到后台队列做解码 DispatchQueue.global(qos: .userInitiated).async { // 把Data转成UIImage的解码操作在这里执行 guard let decodedImage = UIImage(data: item.photoData) else { // 解码失败的情况,回到主线程清空图片 DispatchQueue.main.async { if tableView.indexPath(for: cell) == indexPath { cell.photoImageView.image = nil } } return } // 把解码好的图片存入缓存 self.imageCache.setObject(decodedImage, forKey: cacheKey) // 回到主线程更新UI,同时校验Cell是否还是当前索引(避免Cell复用导致的错误) DispatchQueue.main.async { if tableView.indexPath(for: cell) == indexPath { cell.photoImageView.image = decodedImage } } } // 设置一个占位图,提升用户体验 cell.photoImageView.image = UIImage(named: "placeholder_image") return cell }
3. 额外优化建议
- 提前预解码:如果能预判用户的滚动方向,可以在
tableView(_:willDisplay:forRowAt:)里提前对即将显示的Cell图片做后台解码,进一步减少滚动时的卡顿:func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { let item = messageArray[indexPath.row] let cacheKey = NSString(string: "\(indexPath.row)_image") // 如果缓存里没有,提前在后台解码 if imageCache.object(forKey: cacheKey) == nil { DispatchQueue.global(qos: .userInitiated).async { guard let image = UIImage(data: item.photoData) else { return } self.imageCache.setObject(image, forKey: cacheKey) } } } - 缩小图片尺寸:如果你的ImageView尺寸远小于图片的原始尺寸,建议先在后台把图片缩放到ImageView的大小再解码,能大幅降低解码时间和内存占用(可以用Core Graphics或者ImageIO实现)。
- 杜绝主线程图片操作:任何对图片的裁剪、缩放、滤镜处理,都放到后台队列执行,绝对不要占用主线程资源。
这样修改后,滚动时主线程只负责轻量的UI更新,耗时的解码操作都在后台完成,就能彻底解决滚动抖动的问题啦!
内容的提问来源于stack exchange,提问作者husharoonie
相关产品推荐
相关产品推荐

