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
}
问题
在编写单元测试和模拟 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
}