Skip to content

Commit 2d46703

Browse files
authored
Fix a SignalProducer.lift issue which may leak intermediate signals. (#808)
* Failing test case * Fix a `SignalProducer.lift` issue which may leak intermediate signals. * Update CHANGELOG. * Failing test cases batch 2. * Fix also liftLeft and liftRight.
1 parent c9b8fa5 commit 2d46703

File tree

4 files changed

+77
-5
lines changed

4 files changed

+77
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# master
22
*Please add new entries at the top.*
33
1. Add `ExpressibleByNilLiteral` constraint to `OptionalProtocol` (#805, kudos to @nkristek)
4+
2. Fixed a `SignalProducer.lift` issue which may leak intermediate signals. (#808)
45

56
# 6.4.0
67
1. Bump min. deployment target to iOS 9 when using swift packages to silence Xcode 12 warnings. Update Quick & Nibmle to the latest version when using swift packages.

Sources/SignalProducer.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,7 @@ extension SignalProducer {
666666
return SignalProducer<U, F> { observer, lifetime in
667667
self.startWithSignal { signal, interrupter in
668668
lifetime += interrupter
669-
transform(signal).observe(observer)
669+
lifetime += transform(signal).observe(observer)
670670
}
671671
}
672672
}
@@ -679,15 +679,15 @@ extension SignalProducer {
679679
/// - returns: A factory that creates a SignalProducer with the given operator
680680
/// applied. `self` would be the LHS, and the factory input would
681681
/// be the RHS.
682-
fileprivate func liftLeft<U, F, V, G>(_ transform: @escaping (Signal<Value, Error>) -> (Signal<U, F>) -> Signal<V, G>) -> (SignalProducer<U, F>) -> SignalProducer<V, G> {
682+
internal func liftLeft<U, F, V, G>(_ transform: @escaping (Signal<Value, Error>) -> (Signal<U, F>) -> Signal<V, G>) -> (SignalProducer<U, F>) -> SignalProducer<V, G> {
683683
return { right in
684684
return SignalProducer<V, G> { observer, lifetime in
685685
right.startWithSignal { rightSignal, rightInterrupter in
686686
lifetime += rightInterrupter
687687

688688
self.startWithSignal { leftSignal, leftInterrupter in
689689
lifetime += leftInterrupter
690-
transform(leftSignal)(rightSignal).observe(observer)
690+
lifetime += transform(leftSignal)(rightSignal).observe(observer)
691691
}
692692
}
693693
}
@@ -702,15 +702,15 @@ extension SignalProducer {
702702
/// - returns: A factory that creates a SignalProducer with the given operator
703703
/// applied. `self` would be the LHS, and the factory input would
704704
/// be the RHS.
705-
fileprivate func liftRight<U, F, V, G>(_ transform: @escaping (Signal<Value, Error>) -> (Signal<U, F>) -> Signal<V, G>) -> (SignalProducer<U, F>) -> SignalProducer<V, G> {
705+
internal func liftRight<U, F, V, G>(_ transform: @escaping (Signal<Value, Error>) -> (Signal<U, F>) -> Signal<V, G>) -> (SignalProducer<U, F>) -> SignalProducer<V, G> {
706706
return { right in
707707
return SignalProducer<V, G> { observer, lifetime in
708708
self.startWithSignal { leftSignal, leftInterrupter in
709709
lifetime += leftInterrupter
710710

711711
right.startWithSignal { rightSignal, rightInterrupter in
712712
lifetime += rightInterrupter
713-
transform(leftSignal)(rightSignal).observe(observer)
713+
lifetime += transform(leftSignal)(rightSignal).observe(observer)
714714
}
715715
}
716716
}

Tests/ReactiveSwiftTests/SignalLifetimeSpec.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class SignalLifetimeSpec: QuickSpec {
2424
it("should automatically interrupt if the input observer is not retained") {
2525
let disposable = AnyDisposable()
2626
var outerSignal: Signal<Never, Never>!
27+
_ = outerSignal
2728

2829
func scope() {
2930
let (signal, observer) = Signal<Never, Never>.pipe(disposable: disposable)
@@ -42,6 +43,7 @@ class SignalLifetimeSpec: QuickSpec {
4243
let disposable = AnyDisposable()
4344
var isInterrupted = false
4445
var outerSignal: Signal<Never, Never>!
46+
_ = outerSignal
4547

4648
func scope() {
4749
let (signal, observer) = Signal<Never, Never>.pipe(disposable: disposable)

Tests/ReactiveSwiftTests/SignalProducerSpec.swift

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -849,9 +849,40 @@ class SignalProducerSpec: QuickSpec {
849849

850850
expect(result?.value) == [1, 4, 9, 16]
851851
}
852+
853+
it("should interrupt all intermediate signals when the upstream is terminated") {
854+
let baseProducer = SignalProducer<Int, Never>.empty
855+
856+
var lastEvent: Signal<Int, Never>.Event?
857+
858+
var isNeverEndingSignalDisposed = false
859+
860+
let disposable = baseProducer
861+
.lift { _ in Signal.never.on(disposed: { isNeverEndingSignalDisposed = true }) }
862+
.start { event in
863+
lastEvent = event
864+
}
865+
866+
expect(lastEvent).to(beNil())
867+
expect(isNeverEndingSignalDisposed) == false
868+
869+
disposable.dispose()
870+
expect(lastEvent) == .interrupted
871+
expect(isNeverEndingSignalDisposed) == true
872+
}
852873
}
853874

854875
describe("over binary operators") {
876+
var isNeverEndingSignalDisposed = false
877+
878+
let binaryLiftTransform = { (_: Signal<Int, Never>) -> (Signal<Int, Never>) -> Signal<Int, Never> in
879+
{ (_: Signal<Int, Never>) in
880+
Signal<Int, Never>.never.on(disposed: { isNeverEndingSignalDisposed = true })
881+
}
882+
}
883+
884+
beforeEach { isNeverEndingSignalDisposed = false }
885+
855886
it("should invoke transformation once per started signal") {
856887
let baseProducer = SignalProducer<Int, Never>([1, 2])
857888
let otherProducer = SignalProducer<Int, Never>([3, 4])
@@ -889,6 +920,44 @@ class SignalProducerSpec: QuickSpec {
889920

890921
expect(result?.value) == [5, 7, 9]
891922
}
923+
924+
it("[left associative] should interrupt all intermediate signals when the upstream is terminated") {
925+
let baseProducer = SignalProducer<Int, Never>.empty
926+
927+
var lastEvent: Signal<Int, Never>.Event?
928+
929+
let disposable = baseProducer
930+
.liftLeft(binaryLiftTransform)(.empty)
931+
.start { event in
932+
lastEvent = event
933+
}
934+
935+
expect(lastEvent).to(beNil())
936+
expect(isNeverEndingSignalDisposed) == false
937+
938+
disposable.dispose()
939+
expect(lastEvent) == .interrupted
940+
expect(isNeverEndingSignalDisposed) == true
941+
}
942+
943+
it("[right associative] should interrupt all intermediate signals when the upstream is terminated") {
944+
let baseProducer = SignalProducer<Int, Never>.empty
945+
946+
var lastEvent: Signal<Int, Never>.Event?
947+
948+
let disposable = baseProducer
949+
.liftRight(binaryLiftTransform)(.empty)
950+
.start { event in
951+
lastEvent = event
952+
}
953+
954+
expect(lastEvent).to(beNil())
955+
expect(isNeverEndingSignalDisposed) == false
956+
957+
disposable.dispose()
958+
expect(lastEvent) == .interrupted
959+
expect(isNeverEndingSignalDisposed) == true
960+
}
892961
}
893962

894963
describe("over binary operators with signal") {

0 commit comments

Comments
 (0)