访问swift数组时,swift_isUniquelyReferenced_nonNull_native中的EXC_BA



问题

在编写单元测试和嘲笑NSTimer时,我看到了一个

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

内部

swift_isUniquelyReferenced_nonNull_native

访问此处的数组invalidateInvocations(在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类,因此需要显式init((,但由于这是Timer,它有半抽象的指定初始化器,因此不允许重写(。

解决方案是明确设置遗漏的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
}

最新更新