iOS Swift:图片加载不阻塞UI更新的实现方法咨询
解决图片加载阻塞UI的问题
原代码的核心问题
- 使用
Data(contentsOf:)同步发起网络请求,即使在后台队列,当同时加载多张图片时,会占用大量后台线程资源,间接拖慢主线程UI更新的响应速度 UIImage(data:)创建图片后,在主线程设置时会触发图片解码操作,大图会直接导致主线程卡顿
优化方案
1. 改用异步网络请求+后台解码
替换同步请求为URLSession异步任务,同时在后台完成图片解码,避免主线程负担:
import UIKit extension UIImageView { func load(url: String) { guard let url = URL(string: url) else { return } // 取消之前的请求,避免重复请求和内存泄漏 objc_setAssociatedObject(self, &AssociatedKeys.imageLoadTask, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) let task = URLSession.shared.dataTask(with: url) { [weak self] data, _, error in guard let self = self, let data = data, error == nil, let image = UIImage(data: data) else { return } // 后台线程完成图片解码 let decodedImage = self.decodedImage(from: image) DispatchQueue.main.async { self.image = decodedImage } } task.resume() // 保存任务引用,方便后续取消 objc_setAssociatedObject(self, &AssociatedKeys.imageLoadTask, task, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } // 后台解码图片,避免主线程卡顿 private func decodedImage(from image: UIImage) -> UIImage { guard let cgImage = image.cgImage else { return image } let size = CGSize(width: cgImage.width, height: cgImage.height) let colorSpace = CGColorSpaceCreateDeviceRGB() let context = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: 8, bytesPerRow: cgImage.bytesPerRow, space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue) context?.draw(cgImage, in: CGRect(origin: .zero, size: size)) guard let decodedCGImage = context?.makeImage() else { return image } return UIImage(cgImage: decodedCGImage) } } // 关联对象Key,用于保存网络任务 private enum AssociatedKeys { static var imageLoadTask = "imageLoadTask" }
2. 结合Combine适配你的技术栈
如果要和现有Combine数据流结合,可封装成Combine版本的扩展,更贴合你的开发模式:
import Combine import UIKit extension UIImageView { func loadImage(with urlString: String) -> AnyCancellable? { guard let url = URL(string: urlString) else { return nil } return URLSession.shared.dataTaskPublisher(for: url) .map { UIImage(data: $0.data) } .compactMap { $0 } .map { self.decodedImage(from: $0) } .receive(on: DispatchQueue.main) .sink(receiveCompletion: { _ in }, receiveValue: { [weak self] image in self?.image = image }) } private func decodedImage(from image: UIImage) -> UIImage { guard let cgImage = image.cgImage else { return image } let size = CGSize(width: cgImage.width, height: cgImage.height) let colorSpace = CGColorSpaceCreateDeviceRGB() let context = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: 8, bytesPerRow: cgImage.bytesPerRow, space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue) context?.draw(cgImage, in: CGRect(origin: .zero, size: size)) guard let decodedCGImage = context?.makeImage() else { return image } return UIImage(cgImage: decodedCGImage) } }
使用时在ViewModel或ViewController中管理订阅:
var cancellables = Set<AnyCancellable>() // 加载图片 imageView.loadImage(with: "your_image_url")?.store(in: &cancellables)
3. 额外优化建议
- 增加内存+磁盘缓存,避免重复请求相同图片
- 对于列表类控件,在cell复用时取消之前的图片请求,防止图片错位
- 根据UIImageView的实际尺寸,请求对应分辨率的图片,减少内存占用和解码耗时
内容的提问来源于stack exchange,提问作者Akhmad
相关产品推荐
相关产品推荐

