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

Swift弱属性线程安全问题:EXC_BAD_ACCESS崩溃原因排查

问题背景

以下是实现的Foo类代码:

enum FooState {
    case open
    case closed
}

protocol FooDelegate: AnyObject {
    func foo(_ foo: Foo, didChange state: FooState)
}

class Foo: NSObject {
    private let queue = DispatchQueue(label: "com.test.fooQueue")
    private(set) var state: FooState? {
        didSet {
            guard let state = state else {
                return
            }

            delegate?.foo(self, didChange: state)
        }
    }
    weak var delegate: FooDelegate?

    func close() {
        // Do some extra logic here...
        didChangeFooState(.closed)
    }

    private func didChangeFooState(_ state: FooState) {
        queue.async { [weak self] in
            self?.state = state
        }
    }
}

在App主队列中调用:

foo.close()
foo = nil

大多数情况下代码正常运行,但偶尔会在delegate?.foo(self, didChange: state)行触发EXC_BAD_ACCESS KERN_INVALID_ADDRESS崩溃,疑问如下:

  • 崩溃的原因是什么?我的猜测是state在后台队列赋值,而foo在主队列被销毁引发竞态条件,是否正确?
  • 引用计数是原子性的,这会对该问题产生影响吗?
  • 改用guard let delegate而非可选链式调用,能否解决问题?

问题分析与解答

崩溃原因

你的猜测完全正确,这是竞态条件导致的崩溃:

  • 主队列调用foo.close()后立刻执行foo = nil,此时Foo实例的引用计数被减至0,主队列随即开始销毁该实例。
  • 与此同时,com.test.fooQueue队列中的异步任务可能还在等待执行,或者刚好在实例销毁的过程中执行self?.state = state。此时Foo实例的内存已经开始被回收,触发didSet后访问self或调用delegate方法时,就会访问到已释放的内存,引发EXC_BAD_ACCESS崩溃。

关于引用计数原子性的疑问

引用计数的原子性确实能保证引用计数的增减操作是线程安全的,但它无法解决对象访问的竞态问题:

  • 原子性只是确保多个线程对引用计数的修改不会出现混乱,但当引用计数降至0后,对象的销毁过程是在触发销毁的队列(这里是主队列)上执行的,这个过程和其他队列对对象的访问操作之间没有同步机制,仍然可能出现一个线程在销毁对象,另一个线程同时访问对象的情况。

改用guard let delegate能否解决问题?

不能。崩溃的核心原因是Foo实例本身的内存被提前释放,而非delegate的访问问题。即使你提前用guard let delegate = delegate拿到强引用,调用delegate.foo(self, didChange: state)时,self仍然可能是已被销毁的野指针,崩溃依然会发生。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 10:52:54