Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
** Version 2.12.1 **:

- fix a possible memory leak when the Coordinator's lifecycle was unexpectedly longer than the flow ones (thanks to @asiliuk)

** Version 2.12.0 **:

- bump to RxSwift 6.0.0
Expand Down
2 changes: 1 addition & 1 deletion RxFlow.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "RxFlow"
s.version = "2.12.0"
s.version = "2.12.1"
s.swift_version = '5.3'
s.summary = "RxFlow is a navigation framework for iOS applications, based on a Reactive Coordinator pattern."

Expand Down
4 changes: 2 additions & 2 deletions RxFlow.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 2.11.0;
MARKETING_VERSION = 2.12.1;
PRODUCT_BUNDLE_IDENTIFIER = io.warpfactor.RxFlow;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand Down Expand Up @@ -639,7 +639,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 2.11.0;
MARKETING_VERSION = 2.12.1;
PRODUCT_BUNDLE_IDENTIFIER = io.warpfactor.RxFlow;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand Down
6 changes: 3 additions & 3 deletions RxFlow/FlowCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,11 @@ public final class FlowCoordinator: NSObject {
// listen for the internal steps relay that aggregates the flow's Stepper's steps and
// the FlowContributors's Stepper's steps
self.stepsRelay
.take(until: allowStepWhenDismissed ? .empty() : flow.rxDismissed.asObservable())
.do(onDispose: { [weak self] in
self?.childFlowCoordinators.removeAll()
self?.parentFlowCoordinator?.childFlowCoordinators.removeValue(forKey: self?.identifier ?? "")
})
.asSignal(onErrorJustReturn: NoneStep())
.flatMapLatest { flow.adapt(step: $0).asSignal(onErrorJustReturn: NoneStep()) }
.flatMapLatest { flow.adapt(step: $0) }
.do(onNext: { [weak self] in self?.willNavigateRelay.accept((flow, $0)) })
.map { return (flowContributors: flow.navigate(to: $0), step: $0) }
.do(onNext: { [weak self] in self?.didNavigateRelay.accept((flow, $0.step)) })
Expand Down Expand Up @@ -98,6 +96,8 @@ public final class FlowCoordinator: NSObject {
.flatMap { [weak self] in
self?.steps(from: $0, within: flow, allowStepWhenDismissed: allowStepWhenDismissed) ?? Signal.empty()
}
.take(until: allowStepWhenDismissed ? .empty() : flow.rxDismissed.asObservable())
.asSignal(onErrorJustReturn: NoneStep())
.emit(to: self.stepsRelay)
.disposed(by: self.disposeBag)

Expand Down
47 changes: 47 additions & 0 deletions RxFlowTests/FlowCoordinatorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,28 @@ final class TestDismissedFlow: Flow {
}
}

final class TestLeakingFlow: Flow {
var root: Presentable = UIViewController()

var rxDismissed: Single<Void> { rxDismissedRelay.take(1).asSingle() }
let rxDismissedRelay = PublishRelay<Void>()

func navigate(to step: Step) -> FlowContributors {
guard let step = step as? TestSteps else { return .none }

switch step {
case .one:
let flowContributor = FlowContributor.contribute(
withNextPresentable: UIViewController(),
withNextStepper: DefaultStepper()
)
return .one(flowContributor: flowContributor)
default:
return .none
}
}
}

final class FlowCoordinatorTests: XCTestCase {

func testCoordinateWithOneStepper() {
Expand Down Expand Up @@ -320,6 +342,31 @@ final class FlowCoordinatorTests: XCTestCase {
let recordedSteps = try? dismissedFlow.recordedSteps.take(3).toBlocking().toArray()
XCTAssertEqual(recordedSteps, [.one, .two, .three])
}

func testFlowIsNotLeakingWhenHasOneStep() throws {
weak var leakingFlowReference: TestLeakingFlow?
let exp = expectation(description: "Flow when ready")
let flowCoordinator = FlowCoordinator()

withExtendedLifetime(TestLeakingFlow()) { leakingFlow in
leakingFlowReference = leakingFlow

flowCoordinator.coordinate(flow: leakingFlow,
with: OneStepper(withSingleStep: TestSteps.one))

Flows.use(leakingFlow, when: .created) { (_) in
exp.fulfill()
}
}

waitForExpectations(timeout: 1)

XCTAssertNotNil(leakingFlowReference)

try XCTUnwrap(leakingFlowReference).rxDismissedRelay.accept(Void())

XCTAssertNil(leakingFlowReference)
}
}

#endif