使用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 }) } }
排查思路
- 内存野指针访问:
EXC_BAD_ACCESS通常指向已释放对象的内存访问。虽然ImageFetchOperation强持有imageItem,但如果外部对ImageItem的引用提前释放,或AsyncOperation自身被提前销毁,会导致回调时访问无效内存。 dynamic属性的潜在问题:image属性标记为dynamic,若未使用KVO,该修饰会强制走Runtime消息转发,当对象内存已释放时,消息转发会触发崩溃。- 线程安全问题:
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
相关产品推荐
相关产品推荐

