EXC_BAD_ACCESS 在 swift_isUniquelyReferenced_nonNull_native 访问 swift 数组时

EXC_BAD_ACCESS in swift_isUniquelyReferenced_nonNull_native when accessing swift array

问题

在编写单元测试和模拟 NSTimer 时,我看到

Exception: EXC_BAD_ACCESS (code=1, address=0x8)

里面

swift_isUniquelyReferenced_nonNull_native

这里访问数组invalidateInvocations(inside func invalidate())时出现的情况。

class TimerMock: Timer {

    /// Timer callback type
    typealias TimerCallback = ((Timer) -> Void)

    /// The latest used timer mock accessible to control
    static var  currentTimer: TimerMock!

    /// The block to be invoked on a firing
    private var block:        TimerCallback!

    /// Invalidation invocations (will contain the fireInvocation indices)
    var invalidateInvocations: [Int] = []

    /// Fire invocation count
    var fireInvocations:       Int   = 0

    /// Main function to control a timer fire
    override open func fire() {
        block(self)
        fireInvocations += 1
    }

    /// Hook into invalidation
    override open func invalidate() {
        invalidateInvocations.append(fireInvocations)
    }

    /// Hook into the timer configuration
    override open class func scheduledTimer(withTimeInterval interval: TimeInterval,
                                            repeats: Bool,
                                            block: @escaping TimerCallback) -> Timer {
        // return timer mock
        TimerMock.currentTimer = TimerMock()
        TimerMock.currentTimer.block = block
        return TimerMock.currentTimer
    }

}

有趣的是,如果我将 invalidateInvocations 更改为常规 Int,则可以在没有任何崩溃的情况下访问它。

因为访问这个变量会导致 EXC_BAD_ACCESS 我假设数组已经被释放,但我不明白这是怎么发生的。

演示

您可以在此存储库(分支 demo/crash)中看到完整的 运行 和崩溃示例

https://github.com/nomad5modules/ArcProgressViewIOS/tree/demo/crash

只需执行单元测试并看到它崩溃。

问题

这里发生了什么?我也已经在其他项目中观察到 swift_isUniquelyReferenced_nonNull_native 内部发生崩溃,我很想完全理解这次失败的原因!那么这里找出问题的过程是怎样的呢?以及如何修复它?

独立复制项目

https://drive.google.com/file/d/1fMGhgpmBRG6hzpaiTM9lO_zCZwNhwIpx/view?usp=sharing

崩溃是由于未初始化成员(它是 NSObject 不是常规的 swift class,因此需要显式的 init(),但由于这是 Timer,它具有半抽象指定初始化程序,因此不允许覆盖)。

解决方法是显式设置 missed ivar,如下所示。

已使用 Xcode 11.4.

测试并处理您的测试项目
override open class func scheduledTimer(withTimeInterval interval: TimeInterval,
                                        repeats: Bool,
                                        block: @escaping TimerCallback) -> Timer {
    // return timer mock
    TimerMock.currentTimer = TimerMock()
    TimerMock.currentTimer.invalidateInvocations = [Int]()   // << fix !!
    TimerMock.currentTimer.block = block
    return TimerMock.currentTimer
}