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

使用URLSession/Kingfisher下载图片赋值时触发EXC_BAD_ACCESS崩溃求助

解决iOS中赋值ImageItem.image时的EXC_BAD_ACCESS崩溃问题

问题现象

使用URLSession或Kingfisher下载图片后,赋值给ImageItem的image属性时,均触发EXC_BAD_ACCESS (code=1, address=0x2600010c)崩溃,崩溃位置固定在赋值语句行。

相关代码

ImageItem类定义

@objcMembers class ImageItem: NSObject {
    let imageUrl: String
    dynamic var image: UIImage!

    init(imageUrl: String) {
        self.imageUrl = imageUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
        self.image = nil
    }
    init(imageName: String) {
        self.imageUrl = ""
        self.image = UIImage(named: imageName)
    }
}

URLSession实现代码

class ImageFetchOperation: AsyncOperation {
    let imageItem: ImageItem
    let session: URLSession
    private var dataTask: URLSessionDataTask?
    var menuImageView: UIImageView!
    
    init(imageItem: ImageItem, session: URLSession) {
        self.imageItem = imageItem
        self.session = session
    }
    
    override func main() {
        if let imageUrl = URL(string: self.imageItem.imageUrl) {
            var request = URLRequest(url: imageUrl)
            request.httpMethod = "GET"
            
            dataTask = session.dataTask(with: request, completionHandler: { [weak self] (data, response, error) in
                guard !(self?.isCancelled ?? false) else {
                    self?.state = .isFinished
                    return
                }
                guard let data = data else {
                    self?.state = .isFinished
                    return
                }
                OperationQueue.main.addOperation {
                    self?.imageItem.image = UIImage(data: data) // 崩溃位置
                }
                self?.state = .isFinished
            })
            dataTask?.resume()
        } else {
            self.state = .isFinished
        }
    }
}

Kingfisher实现代码

if let imageUrl = self.imageItem.imageUrl.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed) {
    DispatchQueue.main.async {
        self.menuImageView = UIImageView()
        let url = URL(string: imageUrl)
        self.menuImageView.kf.indicatorType = .activity
        self.menuImageView.kf.setImage(with: url, completionHandler: { [weak self] (image, error, cache, url) in
            self?.imageItem.image = image // 崩溃位置
            self?.state = .isFinished
        })
    }
}

排查思路

  1. 内存野指针访问:EXC_BAD_ACCESS通常指向已释放对象的内存访问。虽然ImageFetchOperation强持有imageItem,但如果外部对ImageItem的引用提前释放,或AsyncOperation自身被提前销毁,会导致回调时访问无效内存。
  2. dynamic属性的潜在问题:image属性标记为dynamic,若未使用KVO,该修饰会强制走Runtime消息转发,当对象内存已释放时,消息转发会触发崩溃。
  3. 线程安全问题:AsyncOperation的state和isCancelled属性若未做线程同步,可能导致多线程下的非法访问,间接引发崩溃。

解决方案

方案1:确保对象生命周期覆盖异步操作

检查外部代码对ImageItem和ImageFetchOperation的引用,确保在图片下载完成前,两者都被强引用持有(例如在UITableView场景中,避免cell复用或销毁时释放关联的ImageItem)。

方案2:优化回调中的安全解包

在赋值前先安全解包self,避免访问已释放的ImageFetchOperation实例:

// URLSession回调修改
OperationQueue.main.addOperation {
    guard let self = self, let image = UIImage(data: data) else { return }
    self.imageItem.image = image
}

// Kingfisher回调修改
self?.menuImageView.kf.setImage(with: url, completionHandler: { [weak self] (image, error, cache, url) in
    guard let self = self else {
        self?.state = .isFinished
        return
    }
    self.imageItem.image = image
    self.state = .isFinished
})

方案3:移除不必要的dynamic修饰

若未对image属性使用KVO,直接移除dynamic关键字,避免Runtime消息转发带来的风险:

@objcMembers class ImageItem: NSObject {
    let imageUrl: String
    var image: UIImage! // 移除dynamic
    
    // ... 其余代码不变
}

方案4:确保AsyncOperation的线程安全

如果自定义AsyncOperation的state管理未加锁,添加线程同步机制(例如用NSLock),避免多线程下的状态竞争:

class AsyncOperation: Operation {
    enum State: String {
        case ready, executing, finished
        fileprivate var keyPath: String { "is\(rawValue.capitalized)" }
    }
    
    var state: State = .ready {
        willSet {
            willChangeValue(forKey: newValue.keyPath)
            willChangeValue(forKey: state.keyPath)
        }
        didSet {
            didChangeValue(forKey: oldValue.keyPath)
            didChangeValue(forKey: state.keyPath)
        }
    }
    
    override var isReady: Bool { super.isReady && state == .ready }
    override var isExecuting: Bool { state == .executing }
    override var isFinished: Bool { state == .finished }
    override var isAsynchronous: Bool { true }
    
    private let lock = NSLock()
    
    override func start() {
        lock.lock()
        guard !isCancelled else {
            state = .finished
            lock.unlock()
            return
        }
        state = .executing
        lock.unlock()
        main()
    }
}

内容的提问来源于stack exchange,提问作者poucher

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 06:01:17