Overview
After updating RxSwift from 5.1.1 to 6.6.0 and the iOS SDK from 16.1 to 17.0, we started encountering sporadic, unexplained crashes (EXC_BAD_ACCESS) related to asynchronous processing.
This issue has been observed exclusively on devices running iOS 16, 17, 18, and 26.
Based on the crash logs, we suspect there is a high probability that the root cause lies in memory management during the deallocation of RxSwift resources—specifically around BinaryDisposable.
Environment
RxSwift: 6.6.0
Build SDK: iOS 17.0 (21A325)
Xcode: 16.x
Affected OS Versions: iOS 16, 17, 18, 26
Firebase SDK: 10.22.0
Problem Details
Since there are no clear steps to reproduce this crash, it has been extremely difficult to replicate in our local development environment.
According to the stack traces obtained from Firebase Crashlytics, EXC_BAD_ACCESS (KERN_INVALID_ADDRESS) occurs during operations like objc_release and ValueWitnesses.destroy.
Crash Logs (Stack Traces)
Stack Trace 1:
Crashed: rxswift.queue.DispatchQoS(qosClass: Dispatch.DispatchQoS.QoSClass.default, relativePriority: 0)
0 libobjc.A.dylib 0x2370 objc_release + 16
1 libobjc.A.dylib 0x2370 objc_release_x0 + 16
2 RxSwift 0x12784 BinaryDisposable.__deallocating_deinit + 10 (BinaryDisposable.swift:10)
3 libswiftCore.dylib 0x3d783c _swift_release_dealloc + 56
4 libswiftCore.dylib 0x3d8950 bool swift::RefCounts<swift::RefCountBitsT<(swift::RefCountInlinedness)1>>::doDecrementSlow<(swift::PerformDeinit)1>(swift::RefCountBitsT<(swift::RefCountInlinedness)1>, unsigned int) + 160
5 libswiftCore.dylib 0x3a51dc assignWithCopy for ClosedRange<>.Index + 208
6 MyApp 0x24dac outlined assign with copy of Disposable? + 4378430892 (<compiler-generated>:4378430892)
7 MyApp 0xada78 closure #1 in closure #1 in MyAppConnect.foo() + 4378991224
8 MyApp 0xbdde4 partial apply for closure #1 in closure #1 in MyAppConnect.foo() + 4379057636 (<compiler-generated>:4379057636)
9 RxSwift 0x38f90 closure #1 in Do.run<A>(_:cancel:) + 109 (Do.swift:109)
10 RxSwift 0x9e18 AnonymousDisposable.dispose() + 44 (AnonymousDisposable.swift:44)
11 RxSwift 0x9edc protocol witness for Disposable.dispose() in conformance AnonymousDisposable + 20 (<compiler-generated>:20)
12 RxSwift 0x60e88 SinkDisposer.dispose() + 88 (Producer.swift:88)
13 RxSwift 0x610a8 protocol witness for Disposable.dispose() in conformance SinkDisposer + 20 (<compiler-generated>:20)
14 RxSwift 0x77370 Sink.dispose() + 48 (Sink.swift:48)
15 RxSwift 0x38ce4 protocol witness for ObserverType.on(_:) in conformance DoSink<A> + 20 (<compiler-generated>:20)
16 RxSwift 0x77294 Sink.forwardOn(_:) + 35 (Sink.swift:35)
17 RxSwift 0x4fdcc closure #2 in MergeSink.on(_:) + 501 (Merge.swift:501)
18 RxSwift 0x4ef80 MergeSink.on(_:) + 20 (Lock.swift:20)
19 RxSwift 0x4fe84 protocol witness for ObserverType.on(_:) in conformance MergeSink<A, B, C> + 20 (<compiler-generated>:20)
20 RxSwift 0x5750c ObserveOnSink.run(_:_:) + 134 (ObserveOn.swift:134)
21 RxSwift 0x58660 partial apply for thunk for @escaping @callee_guaranteed (@guaranteed @callee_guaranteed () -> ()) -> () + 40 (<compiler-generated>:40)
22 RxSwift 0x665c4 partial apply for thunk for @escaping @callee_guaranteed (@in_guaranteed A, @guaranteed @callee_guaranteed @substituted <A> (@in_guaranteed A) -> () for <A>) -> () + 52 (<compiler-generated>:52)
23 RxSwift 0x65f84 closure #1 in RecursiveImmediateScheduler.schedule(_:) + 175 (RecursiveScheduler.swift:175)
24 RxSwift 0x36720 closure #1 in DispatchQueueConfiguration.schedule<A>(_:action:) + 27 (DispatchQueueConfiguration.swift:27)
25 RxSwift 0x2cb10 thunk for @escaping @callee_guaranteed @Sendable () -> () + 28 (<compiler-generated>:28)
26 libdispatch.dylib 0x2370 _dispatch_call_block_and_release + 32
27 libdispatch.dylib 0x40d0 _dispatch_client_callout + 20
28 libdispatch.dylib 0x7580 _dispatch_continuation_pop + 596
29 libdispatch.dylib 0x6bb0 _dispatch_async_redirect_invoke + 580
30 libdispatch.dylib 0x15d8c _dispatch_root_queue_drain + 392
31 libdispatch.dylib 0x16590 _dispatch_worker_thread2 + 156
32 libsystem_pthread.dylib 0x4c40 _pthread_wqthread + 228
33 libsystem_pthread.dylib 0x1488 start_wqthread + 8
Stack Trace 2:
Crashed: rxswift.queue.DispatchQoS(qosClass: Dispatch.DispatchQoS.QoSClass.default, relativePriority: 0)
0 libswiftCore.dylib 0x406e24 swift::metadataimpl::ValueWitnesses<swift::metadataimpl::OpaqueExistentialBox<1u>>::destroy(swift::OpaqueValue*, swift::TargetMetadata<swift::InProcess> const*) + 8
1 RxSwift 0x1035c outlined destroy of Disposable? + 48 (<compiler-generated>:48)
2 RxSwift 0x1278c BinaryDisposable.__deallocating_deinit + 10 (BinaryDisposable.swift:10)
3 libswiftCore.dylib 0x3d783c _swift_release_dealloc + 56
4 libswiftCore.dylib 0x3d8950 bool swift::RefCounts<swift::RefCountBitsT<(swift::RefCountInlinedness)1>>::doDecrementSlow<(swift::PerformDeinit)1>(swift::RefCountBitsT<(swift::RefCountInlinedness)1>, unsigned int) + 160
5 libswiftCore.dylib 0x3a51dc assignWithCopy for ClosedRange<>.Index + 208
6 MyApp 0x24dac outlined assign with copy of Disposable? + 4295396780 (<compiler-generated>:4295396780)
7 MyApp 0xada78 closure #1 in closure #1 in MyAppConnect.foo() + 4295957112
8 MyApp 0xbce8c partial apply for closure #4 in MyAppConnect.bar() + 4296019596 (<compiler-generated>:4296019596)
9 RxSwift 0x38f90 closure #1 in Do.run<A>(_:cancel:) + 109 (Do.swift:109)
10 RxSwift 0x9e18 AnonymousDisposable.dispose() + 44 (AnonymousDisposable.swift:44)
11 RxSwift 0x9edc protocol witness for Disposable.dispose() in conformance AnonymousDisposable + 20 (<compiler-generated>:20)
12 RxSwift 0x60e88 SinkDisposer.dispose() + 88 (Producer.swift:88)
13 RxSwift 0x610a8 protocol witness for Disposable.dispose() in conformance SinkDisposer + 20 (<compiler-generated>:20)
14 RxSwift 0x77370 Sink.dispose() + 48 (Sink.swift:48)
15 RxSwift 0x38ce4 protocol witness for ObserverType.on(_:) in conformance DoSink<A> + 20 (<compiler-generated>:20)
16 RxSwift 0x77294 Sink.forwardOn(_:) + 35 (Sink.swift:35)
17 RxSwift 0x4fdcc closure #2 in MergeSink.on(_:) + 501 (Merge.swift:501)
18 RxSwift 0x4ef80 MergeSink.on(_:) + 20 (Lock.swift:20)
19 RxSwift 0x4fe84 protocol witness for ObserverType.on(_:) in conformance MergeSink<A, B, C> + 20 (<compiler-generated>:20)
20 RxSwift 0x5750c ObserveOnSink.run(_:_:) + 134 (ObserveOn.swift:134)
21 RxSwift 0x58660 partial apply for thunk for @escaping @callee_guaranteed (@guaranteed @callee_guaranteed () -> ()) -> () + 40 (<compiler-generated>:40)
22 RxSwift 0x665c4 partial apply for thunk for @escaping @callee_guaranteed (@in_guaranteed A, @guaranteed @callee_guaranteed @substituted <A> (@in_guaranteed A) -> () for <A>) -> () + 52 (<compiler-generated>:52)
23 RxSwift 0x65f84 closure #1 in RecursiveImmediateScheduler.schedule(_:) + 175 (RecursiveScheduler.swift:175)
24 RxSwift 0x36720 closure #1 in DispatchQueueConfiguration.schedule<A>(_:action:) + 27 (DispatchQueueConfiguration.swift:27)
25 RxSwift 0x2cb10 thunk for @escaping @callee_guaranteed @Sendable () -> () + 28 (<compiler-generated>:28)
26 libdispatch.dylib 0x2370 _dispatch_call_block_and_release + 32
27 libdispatch.dylib 0x40d0 _dispatch_client_callout + 20
28 libdispatch.dylib 0x7580 _dispatch_continuation_pop + 596
29 libdispatch.dylib 0x6bb0 _dispatch_async_redirect_invoke + 580
30 libdispatch.dylib 0x15d8c _dispatch_root_queue_drain + 392
31 libdispatch.dylib 0x16590 _dispatch_worker_thread2 + 156
32 libsystem_pthread.dylib 0x4c40 _pthread_wqthread + 228
33 libsystem_pthread.dylib 0x1488 start_wqthread + 8
Stack Trace 3:
Crashed: rxswift.queue.DispatchQoS(qosClass: Dispatch.DispatchQoS.QoSClass.default, relativePriority: 0)
0 libobjc.A.dylib 0x4f1c objc_destructInstance + 32
1 libswiftCore.dylib 0x3d8230 swift_deallocClassInstance + 120
2 libswiftCore.dylib 0x3d817c _swift_release_dealloc + 56
3 libswiftCore.dylib 0x3d9290 bool swift::RefCounts<swift::RefCountBitsT<(swift::RefCountInlinedness)1>>::doDecrementSlow<(swift::PerformDeinit)1>(swift::RefCountBitsT<(swift::RefCountInlinedness)1>, unsigned int) + 160
4 libswiftCore.dylib 0x3a5ad8 assignWithCopy for ClosedRange<>.Index + 208
5 MyApp 0x20dac outlined assign with copy of Disposable? + 4341616044 (<compiler-generated>:4341616044)
6 MyApp 0xa8c0c closure #1 in closure #1 in MyAppConnect.foo() + 4342172684
7 MyApp 0xb80b4 partial apply for closure #1 in MyAppConnect.bar() + 4342235316 (<compiler-generated>:4342235316)
8 RxSwift 0x38f90 closure #1 in Do.run<A>(_:cancel:) + 109 (Do.swift:109)
9 RxSwift 0x9e18 AnonymousDisposable.dispose() + 44 (AnonymousDisposable.swift:44)
10 RxSwift 0x9edc protocol witness for Disposable.dispose() in conformance AnonymousDisposable + 20 (<compiler-generated>:20)
11 RxSwift 0x60e88 SinkDisposer.dispose() + 88 (Producer.swif
12 RxSwift 0x610a8 protocol witness for Disposable.dispose() in conformance SinkDisposer + 20 (<compiler-generated>:20)
13 RxSwift 0x77370 Sink.dispose() + 48 (Sink.swift:48)
14 RxSwift 0x38ce4 protocol witness for ObserverType.on(_:) in conformance DoSink<A> + 20 (<compiler-generated>:20)
15 RxSwift 0x77294 Sink.forwardOn(_:) + 35 (Sink.swift:35)
16 RxSwift 0x4fdcc closure #2 in MergeSink.on(_:) + 501 (Merge.swift:501)
17 RxSwift 0x4ef80 MergeSink.on(_:) + 20 (Lock.swift:20)
18 RxSwift 0x4fe84 protocol witness for ObserverType.on(_:) in conformance MergeSink<A, B, C> + 20 (<compiler-generated>:20)
19 RxSwift 0x5750c ObserveOnSink.run(_:_:) + 134 (ObserveOn.swift:134)
20 RxSwift 0x58660 partial apply for thunk for @escaping @callee_guaranteed (@guaranteed @callee_guaranteed () -> ()) -> () + 40 (<compiler-generated>:40)
21 RxSwift 0x665c4 partial apply for thunk for @escaping @callee_guaranteed (@in_guaranteed A, @guaranteed @callee_guaranteed @substituted <A> (@in_guaranteed A) -> () for <A>) -> () + 52 (<compiler-generated>:52)
22 RxSwift 0x65f84 closure #1 in RecursiveImmediateScheduler.schedule(_:) + 175 (RecursiveScheduler.swift:175)
23 RxSwift 0x36720 closure #1 in DispatchQueueConfiguration.schedule<A>(_:action:) + 27 (DispatchQueueConfiguration.swift:27)
24 RxSwift 0x2cb10 thunk for @escaping @callee_guaranteed @Sendable () -> () + 28 (<compiler-generated>:28)
25 libdispatch.dylib 0x2248 _dispatch_call_block_and_release + 32
26 libdispatch.dylib 0x3fa8 _dispatch_client_callout + 20
27 libdispatch.dylib 0x745c _dispatch_continuation_pop + 596
28 libdispatch.dylib 0x6a8c _dispatch_async_redirect_invoke + 580
29 libdispatch.dylib 0x15ec0 _dispatch_root_queue_drain + 392
30 libdispatch.dylib 0x166c4 _dispatch_worker_thread2 + 156
31 libsystem_pthread.dylib 0x3644 _pthread_wqthread + 228
32 libsystem_pthread.dylib 0x1474 start_wqthread + 8
Stack Trace 4:
Crashed: rxswift.queue.DispatchQoS(qosClass: Dispatch.DispatchQoS.QoSClass.default, relativePriority: 0)
0 libswiftCore.dylib 0x3d7824 _swift_release_dealloc + 32
1 libswiftCore.dylib 0x3d8950 bool swift::RefCounts<swift::RefCountBitsT<(swift::RefCountInlinedness)1>>::doDecrementSlow<(swift::PerformDeinit)1>(swift::RefCountBitsT<(swift::RefCountInlinedness)1>, unsigned int) + 160
2 libswiftCore.dylib 0x3a51dc assignWithCopy for ClosedRange<>.Index + 208
3 MyApp 0x20dac outlined assign with copy of Disposable? + 4333276588 (<compiler-generated>:4333276588)
4 MyApp 0xa8c0c closure #1 in closure #1 in MyAppConnect.foo() + 4333833228
5 MyApp 0xb80b4 partial apply for closure #1 in MyAppConnect.bar() + 4333895860 (<compiler-generated>:4333895860)
6 RxSwift 0x38f90 closure #1 in Do.run<A>(_:cancel:) + 109 (Do.swift:109)
7 RxSwift 0x9e18 AnonymousDisposable.dispose() + 44 (AnonymousDisposable.swift:44)
8 RxSwift 0x9edc protocol witness for Disposable.dispose() in conformance AnonymousDisposable + 20 (<compiler-generated>:20)
9 RxSwift 0x60e88 SinkDisposer.dispose() + 88 (Producer.swift:88)
10 RxSwift 0x610a8 protocol witness for Disposable.dispose() in conformance SinkDisposer + 20 (<compiler-generated>:20)
11 RxSwift 0x77370 Sink.dispose() + 48 (Sink.swift:48)
12 RxSwift 0x38ce4 protocol witness for ObserverType.on(_:) in conformance DoSink<A> + 20 (<compiler-generated>:20)
13 RxSwift 0x77294 Sink.forwardOn(_:) + 35 (Sink.swift:35)
14 RxSwift 0x4fdcc closure #2 in MergeSink.on(_:) + 501 (Merge.swift:501)
15 RxSwift 0x4ef80 MergeSink.on(_:) + 20 (Lock.swift:20)
16 RxSwift 0x4fe84 protocol witness for ObserverType.on(_:) in conformance MergeSink<A, B, C> + 20 (<compiler-generated>:20)
17 RxSwift 0x5750c ObserveOnSink.run(_:_:) + 134 (ObserveOn.swift:134)
18 RxSwift 0x58660 partial apply for thunk for @escaping @callee_guaranteed (@guaranteed @callee_guaranteed () -> ()) -> () + 40 (<compiler-generated>:40)
19 RxSwift 0x665c4 partial apply for thunk for @escaping @callee_guaranteed (@in_guaranteed A, @guaranteed @callee_guaranteed @substituted <A> (@in_guaranteed A) -> () for <A>) -> () + 52 (<compiler-generated>:52)
20 RxSwift 0x65f84 closure #1 in RecursiveImmediateScheduler.schedule(_:) + 175 (RecursiveScheduler.swift:175)
21 RxSwift 0x36720 closure #1 in DispatchQueueConfiguration.schedule<A>(_:action:) + 27 (DispatchQueueConfiguration.swift:27)
22 RxSwift 0x2cb10 thunk for @escaping @callee_guaranteed @Sendable () -> () + 28 (<compiler-generated>:28)
23 libdispatch.dylib 0x2370 _dispatch_call_block_and_release + 32
24 libdispatch.dylib 0x40d0 _dispatch_client_callout + 20
25 libdispatch.dylib 0x7580 _dispatch_continuation_pop + 596
26 libdispatch.dylib 0x6bb0 _dispatch_async_redirect_invoke + 580
27 libdispatch.dylib 0x15d8c _dispatch_root_queue_drain + 392
28 libdispatch.dylib 0x16590 _dispatch_worker_thread2 + 156
29 libsystem_pthread.dylib 0x4c40 _pthread_wqthread + 228
30 libsystem_pthread.dylib 0x1488 start_wqthread + 8
Investigation Details
We attempted to compile the project in Xcode 16 with Strict Concurrency Checking set to Complete.
While we expected a large volume of warnings, no concurrency-related warnings were flagged around the MyAppConnect class, which appears to be the starting point of the crash.
Questions
Are there any known compatibility issues regarding lifecycle management of BinaryDisposable between RxSwift 6.6.0 and the Swift runtime environment in iOS 16–26?
Our current hypothesis is that a data race/race condition is occurring between the resource disposal process on a background thread and the deallocation of the parent object on the Main Actor (Main Thread).
If anyone has encountered a similar issue or could provide guidance on effective debugging techniques for this scenario, your insights would be highly appreciated.
Overview
After updating RxSwift from 5.1.1 to 6.6.0 and the iOS SDK from 16.1 to 17.0, we started encountering sporadic, unexplained crashes (EXC_BAD_ACCESS) related to asynchronous processing.
This issue has been observed exclusively on devices running iOS 16, 17, 18, and 26.
Based on the crash logs, we suspect there is a high probability that the root cause lies in memory management during the deallocation of RxSwift resources—specifically around BinaryDisposable.
Environment
RxSwift: 6.6.0
Build SDK: iOS 17.0 (21A325)
Xcode: 16.x
Affected OS Versions: iOS 16, 17, 18, 26
Firebase SDK: 10.22.0
Problem Details
Since there are no clear steps to reproduce this crash, it has been extremely difficult to replicate in our local development environment.
According to the stack traces obtained from Firebase Crashlytics, EXC_BAD_ACCESS (KERN_INVALID_ADDRESS) occurs during operations like objc_release and ValueWitnesses.destroy.
Crash Logs (Stack Traces)
Stack Trace 1:
Stack Trace 2:
Stack Trace 3:
Stack Trace 4:
Investigation Details
We attempted to compile the project in Xcode 16 with Strict Concurrency Checking set to Complete.
While we expected a large volume of warnings, no concurrency-related warnings were flagged around the MyAppConnect class, which appears to be the starting point of the crash.
Questions
Are there any known compatibility issues regarding lifecycle management of BinaryDisposable between RxSwift 6.6.0 and the Swift runtime environment in iOS 16–26?
Our current hypothesis is that a data race/race condition is occurring between the resource disposal process on a background thread and the deallocation of the parent object on the Main Actor (Main Thread).
If anyone has encountered a similar issue or could provide guidance on effective debugging techniques for this scenario, your insights would be highly appreciated.