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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 14:55:08