Skip to content

Commit

Permalink
Fix: Factory closure lifetime differs between .create and .deferred, …
Browse files Browse the repository at this point in the history
…subscriptions to the latter keep its factory closure alive #2533 (#2534)
  • Loading branch information
danielt1263 authored Oct 28, 2023
1 parent 473b7a7 commit 214f7ff
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 11 deletions.
20 changes: 9 additions & 11 deletions RxSwift/Observables/Deferred.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,16 @@ extension ObservableType {
}

final private class DeferredSink<Source: ObservableType, Observer: ObserverType>: Sink<Observer>, ObserverType where Source.Element == Observer.Element {
typealias Element = Observer.Element

private let observableFactory: () throws -> Source

init(observableFactory: @escaping () throws -> Source, observer: Observer, cancel: Cancelable) {
self.observableFactory = observableFactory
typealias Element = Observer.Element
typealias Parent = Deferred<Source>

override init(observer: Observer, cancel: Cancelable) {
super.init(observer: observer, cancel: cancel)
}

func run() -> Disposable {
func run(_ parent: Parent) -> Disposable {
do {
let result = try self.observableFactory()
let result = try parent.observableFactory()
return result.subscribe(self)
}
catch let e {
Expand All @@ -60,16 +58,16 @@ final private class DeferredSink<Source: ObservableType, Observer: ObserverType>
final private class Deferred<Source: ObservableType>: Producer<Source.Element> {
typealias Factory = () throws -> Source

private let observableFactory : Factory
let observableFactory : Factory

init(observableFactory: @escaping Factory) {
self.observableFactory = observableFactory
}

override func run<Observer: ObserverType>(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable)
where Observer.Element == Source.Element {
let sink = DeferredSink(observableFactory: self.observableFactory, observer: observer, cancel: cancel)
let subscription = sink.run()
let sink = DeferredSink<Source, Observer>(observer: observer, cancel: cancel)
let subscription = sink.run(self)
return (sink: sink, subscription: subscription)
}
}
72 changes: 72 additions & 0 deletions Tests/RxSwiftTests/Observable+Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,78 @@ extension ObservableTest {
}
}

// MARK: - Deferred
extension ObservableTest {
func testDeferredFactoryClosureLifetime() {
class Foo {
let expectation: XCTestExpectation

init(expectation: XCTestExpectation) {
self.expectation = expectation
}

func bar() -> Observable<Void> {
Observable<Void>
.deferred {
self.expectation.fulfill()
return .never()
}
}
}

let factoryClosureInvoked = expectation(description: "Factory closure has been invoked")
var foo: Foo? = Foo(expectation: factoryClosureInvoked)
weak var initialFoo = foo

let disposable = foo?.bar().subscribe()

wait(for: [factoryClosureInvoked])

// reset foo to let the initial instance deallocate
foo = nil

// we know that the factory closure has already been executed,
// and the foo reference has been nilled, so there should be nothing
// keeping the object alive
XCTAssertNil(initialFoo)

disposable?.dispose()
}

func testObservableFactoryClosureLifetime() {
class Foo {
let expectation: XCTestExpectation

init(expectation: XCTestExpectation) {
self.expectation = expectation
}

func bar() -> Observable<Void> {
Observable<Void>
.create { _ in
self.expectation.fulfill()
return Disposables.create()
}
}
}

let factoryClosureInvoked = expectation(description: "Factory closure has been invoked")
var foo: Foo? = Foo(expectation: factoryClosureInvoked)
weak var initialFoo = foo

let disposable = foo?.bar().subscribe()

wait(for: [factoryClosureInvoked])

// reset foo to let the initial instance deallocate
foo = nil

XCTAssertNil(initialFoo)

disposable?.dispose()
}
}

private class TestObject: NSObject {
var id = UUID()
}

0 comments on commit 214f7ff

Please sign in to comment.