While writing unit tests and mocking away the NSTimer
I am seeing an
Exception: EXC_BAD_ACCESS (code=1, address=0x8)
inside
swift_isUniquelyReferenced_nonNull_native
The situation appear when accessing the array invalidateInvocations
(inside func invalidate()
) here.
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
}
}
The interesting thing is if I change invalidateInvocations
to a regular Int
it can be accessed without any crashes.
Because accessing this variable leads to a EXC_BAD_ACCESS
I would assume the array was already deallocated, but I would not see how this could happen.
You can see a full running and crashing example in this repository (branch demo/crash
)
https://github.com/nomad5modules/ArcProgressViewIOS/tree/demo/crash
Simply execute the unit tests and see it crashing.
What is happening here? I have observed a crash inside swift_isUniquelyReferenced_nonNull_native
already in other projects too and I would love to completely understand the reason for this failure! So how is the process to find out what wrong here? And how to fix it?
https://drive.google.com/file/d/1fMGhgpmBRG6hzpaiTM9lO_zCZwNhwIpx/view?usp=sharing
The crash is due to not initialised member (it is NSObject not regular swift class, so explicit init() would be needed, but as this is Timer it has semi-abstract designated initializer, so override is not allowed).
The solution is to set up missed ivar explicitly, as below.
Tested & works on your Test project with 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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With