Consider the following code:
class RxDisposeTest : XCTestCase {
func test_dispose() {
var scope = DisposeBag()
var subjects = [PublishSubject<String>]()
let upstream = PublishSubject<Int>()
upstream
.flatMapLatest { _ in
let newSubject = PublishSubject<String>()
subjects.append(newSubject)
return newSubject.debug("\(Unmanaged.passUnretained(newSubject).toOpaque())").do(onDispose: { print("onDispose :: ", newSubject.isDisposed) })
}
.subscribe()
.disposed(by: scope)
upstream.onNext(1)
upstream.onNext(2)
XCTAssertEqual(subjects.dropLast().last?.isDisposed, true) // fails
XCTAssertEqual(subjects.last?.isDisposed, false)
scope = .init()
XCTAssertEqual(subjects.last?.isDisposed, true) // fails
}
}
Running this test prints out the following:
Test Case '-[CommonTests.RxDisposeTest test_dispose]' started.
2023-06-21 11:36:30.625: 0x0000600002c34f00 -> subscribed
2023-06-21 11:36:30.625: 0x0000600002c34f00 -> isDisposed
onDispose :: false
2023-06-21 11:36:30.626: 0x0000600002c34580 -> subscribed
RxDisposeTest.swift:257: error: -[CommonTests.RxDisposeTest test_dispose] : XCTAssertEqual failed: ("Optional(false)") is not equal to ("Optional(true)")
2023-06-21 11:36:32.679: 0x0000600002c34580 -> isDisposed
onDispose :: false
RxDisposeTest.swift:262: error: -[CommonTests.RxDisposeTest test_dispose] : XCTAssertEqual failed: ("Optional(false)") is not equal to ("Optional(true)")
As you can see, checking isDisposed
inside the onDispose
hook returns false
.
Also, debug()
correctly prints out that the first PublishSubject
is disposed as the second one takes over inside flatMapLatest
. Is this expected behavior?
The version of RxSwift is 5.1.2.
Yes, this is expected behavior. That particular subscription to the subject has been disposed, but the subject itself is still viable and can accept onNext
events.
Remember, a Subject can have many subscriptions attached to it. Just because a particular subscription is disposed doesn't mean the entire subject is. A Subject is only disposed when its dispose method is called.
Check out this test:
func test_dispose() {
var scope = DisposeBag()
var subjects = [PublishSubject<String>]()
let upstream = PublishSubject<Int>()
scope.insert(upstream
.flatMapLatest { [weak scope] count in
let newSubject = PublishSubject<String>()
subjects.append(newSubject)
scope?.insert(newSubject) // here we are inserting the subject into the dispose bag.
return newSubject
.debug("subscription \(count)")
}
.subscribe()
)
upstream.onNext(1)
upstream.onNext(2)
XCTAssertEqual(subjects.count, 2)
XCTAssertFalse(subjects.map(\.isDisposed).allSatisfy { $0 })
scope = .init()
XCTAssertTrue(subjects.map(\.isDisposed).allSatisfy { $0 })
}