From 0e08a57d317cf243f55bde3386cf78747ec1666b Mon Sep 17 00:00:00 2001 From: Nikolas Mayr Date: Thu, 13 Feb 2020 15:30:05 +0100 Subject: [PATCH 1/5] joining an empty list of producers returns a single event of the collected results, which is the empty array - this becomes relevant, when the list of producers is calculated and you are merging the resulting joined signal with some other signal. the end result will stall, if no event is sent from the joined producers. --- Sources/SignalProducer.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/SignalProducer.swift b/Sources/SignalProducer.swift index f190c9017..4f549fd7f 100644 --- a/Sources/SignalProducer.swift +++ b/Sources/SignalProducer.swift @@ -2188,6 +2188,7 @@ extension SignalProducer { } guard !setup.isEmpty else { + observer.send(value: []) observer.sendCompleted() return } From 7c188109071c5ab84829d34ed337740d1382204d Mon Sep 17 00:00:00 2001 From: Nikolas Mayr Date: Thu, 13 Feb 2020 15:37:37 +0100 Subject: [PATCH 2/5] updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5aea41fc..fa06b81cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # master *Please add new entries at the top.* +1. Joining an empty list of producers results in sending an event on the joined signal which contains the empty list, before the signal is completed. This becomes relevant, when the list of producers is calculated from some other Signal and the signal resulting from the joined producers is observed. If no event is sent only when the producers list is empty, then the observer gets staleld and e.g. the ui won't update. 1. Fixed `SignalProducer.debounce` operator that, when started more than once, would not deliver values on producers started after the first time. (#772, kudos to @gpambrozio) 1. `FlattenStrategy.throttle` is introduced. (#713, kudos to @inamiy) 1. Updated `README.md` to reflect Swift 5.1 compatibility and point snippets to 6.1.0 (#763, kudos to @Marcocanc) From 3d026d3d15a7b312645373439f9b4bc4373d31a4 Mon Sep 17 00:00:00 2001 From: Nikolas Mayr Date: Tue, 18 Feb 2020 12:31:32 +0100 Subject: [PATCH 3/5] no upstream sentinal is optional --- Sources/Property.swift | 8 ++++---- Sources/SignalProducer.swift | 18 ++++++++++-------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Sources/Property.swift b/Sources/Property.swift index a5c62fe8b..8eba65e5e 100644 --- a/Sources/Property.swift +++ b/Sources/Property.swift @@ -318,13 +318,13 @@ extension PropertyProtocol { /// Combines the values of all the given producers, in the manner described by /// `combineLatest(with:)`. Returns nil if the sequence is empty. - public static func combineLatest(_ properties: S) -> Property<[S.Iterator.Element.Value]>? where S.Iterator.Element: PropertyProtocol { + public static func combineLatest(_ properties: S, noUpstreamSentinel: [S.Iterator.Element.Value]? = nil) -> Property<[S.Iterator.Element.Value]>? where S.Iterator.Element: PropertyProtocol { let producers = properties.map { $0.producer } guard !producers.isEmpty else { return nil } - return Property(unsafeProducer: SignalProducer.combineLatest(producers)) + return Property(unsafeProducer: SignalProducer.combineLatest(producers, noUpstreamSentinel: noUpstreamSentinel)) } /// Zips the values of all the given properties, in the manner described by @@ -383,13 +383,13 @@ extension PropertyProtocol { /// Zips the values of all the given properties, in the manner described by /// `zip(with:)`. Returns nil if the sequence is empty. - public static func zip(_ properties: S) -> Property<[S.Iterator.Element.Value]>? where S.Iterator.Element: PropertyProtocol { + public static func zip(_ properties: S, noUpstreamSentinel: [S.Iterator.Element.Value]? = nil) -> Property<[S.Iterator.Element.Value]>? where S.Iterator.Element: PropertyProtocol { let producers = properties.map { $0.producer } guard !producers.isEmpty else { return nil } - return Property(unsafeProducer: SignalProducer.zip(producers)) + return Property(unsafeProducer: SignalProducer.zip(producers, noUpstreamSentinel: noUpstreamSentinel)) } } diff --git a/Sources/SignalProducer.swift b/Sources/SignalProducer.swift index 4f549fd7f..727104bff 100644 --- a/Sources/SignalProducer.swift +++ b/Sources/SignalProducer.swift @@ -2098,8 +2098,8 @@ extension SignalProducer { /// Combines the values of all the given producers, in the manner described by /// `combineLatest(with:)`. Will return an empty `SignalProducer` if the sequence is empty. - public static func combineLatest(_ producers: S) -> SignalProducer<[Value], Error> where S.Iterator.Element: SignalProducerConvertible, S.Iterator.Element.Value == Value, S.Iterator.Element.Error == Error { - return start(producers, Signal.combineLatest) + public static func combineLatest(_ producers: S, noUpstreamSentinel: [S.Iterator.Element.Value]? = nil) -> SignalProducer<[Value], Error> where S.Iterator.Element: SignalProducerConvertible, S.Iterator.Element.Value == Value, S.Iterator.Element.Error == Error { + return start(producers, noUpstreamSentinel: noUpstreamSentinel, Signal.combineLatest) } /// Zips the values of all the given producers, in the manner described by @@ -2176,11 +2176,11 @@ extension SignalProducer { /// Zips the values of all the given producers, in the manner described by /// `zipWith`. Will return an empty `SignalProducer` if the sequence is empty. - public static func zip(_ producers: S) -> SignalProducer<[Value], Error> where S.Iterator.Element: SignalProducerConvertible, S.Iterator.Element.Value == Value, S.Iterator.Element.Error == Error { - return start(producers, Signal.zip) + public static func zip(_ producers: S, noUpstreamSentinel: [S.Iterator.Element.Value]? = nil) -> SignalProducer<[Value], Error> where S.Iterator.Element: SignalProducerConvertible, S.Iterator.Element.Value == Value, S.Iterator.Element.Error == Error { + return start(producers, noUpstreamSentinel: noUpstreamSentinel, Signal.zip) } - private static func start(_ producers: S, _ transform: @escaping (AnySequence>) -> Signal<[Value], Error>) -> SignalProducer<[Value], Error> where S.Iterator.Element: SignalProducerConvertible, S.Iterator.Element.Value == Value, S.Iterator.Element.Error == Error + private static func start(_ producers: S, noUpstreamSentinel: [S.Iterator.Element.Value]?, _ transform: @escaping (AnySequence>) -> Signal<[Value], Error>) -> SignalProducer<[Value], Error> where S.Iterator.Element: SignalProducerConvertible, S.Iterator.Element.Value == Value, S.Iterator.Element.Error == Error { return SignalProducer<[Value], Error> { observer, lifetime in let setup = producers.map { @@ -2188,7 +2188,9 @@ extension SignalProducer { } guard !setup.isEmpty else { - observer.send(value: []) + if let noUpstreamSentinel = noUpstreamSentinel { + observer.send(value: noUpstreamSentinel) + } observer.sendCompleted() return } @@ -2718,7 +2720,7 @@ extension SignalProducer where Value == Bool { /// /// - returns: A producer that emits the logical AND results. public static func all(_ booleans: BooleansCollection) -> SignalProducer where BooleansCollection.Element == SignalProducer { - return combineLatest(booleans).map { $0.reduce(true) { $0 && $1 } } + return combineLatest(booleans, noUpstreamSentinel: []).map { $0.reduce(true) { $0 && $1 } } } /// Create a producer that computes a logical AND between the latest values of `booleans`. @@ -2760,7 +2762,7 @@ extension SignalProducer where Value == Bool { /// /// - returns: A producer that emits the logical OR results. public static func any(_ booleans: BooleansCollection) -> SignalProducer where BooleansCollection.Element == SignalProducer { - return combineLatest(booleans).map { $0.reduce(false) { $0 || $1 } } + return combineLatest(booleans, noUpstreamSentinel: []).map { $0.reduce(false) { $0 || $1 } } } /// Create a producer that computes a logical OR between the latest values of `booleans`. From 81e26b706a1129ca7d4b9cda5294112eaa5383c0 Mon Sep 17 00:00:00 2001 From: Nikolas Mayr Date: Tue, 18 Feb 2020 12:35:59 +0100 Subject: [PATCH 4/5] updated changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45f62dfc0..1a21ec0e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # master *Please add new entries at the top.* -1. Joining an empty list of producers results in sending an event on the joined signal which contains the empty list, before the signal is completed. This becomes relevant, when the list of producers is calculated from some other Signal and the signal resulting from the joined producers is observed. If no event is sent only when the producers list is empty, then the observer gets staleld and e.g. the ui won't update. +1. Joining an empty sequence of producers can now send an event on the joined signal producer by providing the `noUpstreamSentinal` parameter. This becomes relevant, when the sequence of producers is calculated from some other Signal and the signal resulting from the joined producers is observed. If no event is sent only when the producers sequence is empty, then the observer gets stalled and e.g. the ui won't update. 1. Improved performance of joining signals by a factor of around 5. This enables joining of 1000 and more signals in a reasonable amount of time. 1. Fixed `SignalProducer.debounce` operator that, when started more than once, would not deliver values on producers started after the first time. (#772, kudos to @gpambrozio) 1. `FlattenStrategy.throttle` is introduced. (#713, kudos to @inamiy) From 7e9219057c5e068ce8111cbd948910469e125431 Mon Sep 17 00:00:00 2001 From: Nikolas Mayr Date: Tue, 18 Feb 2020 12:39:03 +0100 Subject: [PATCH 5/5] typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a21ec0e1..70daa2b07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # master *Please add new entries at the top.* -1. Joining an empty sequence of producers can now send an event on the joined signal producer by providing the `noUpstreamSentinal` parameter. This becomes relevant, when the sequence of producers is calculated from some other Signal and the signal resulting from the joined producers is observed. If no event is sent only when the producers sequence is empty, then the observer gets stalled and e.g. the ui won't update. +1. Joining an empty sequence of producers can now send an event on the joined signal producer by providing the `noUpstreamSentinel` parameter. This becomes relevant, when the sequence of producers is calculated from some other Signal and the signal resulting from the joined producers is observed. If no event is sent only when the producers sequence is empty, then the observer gets stalled and e.g. the ui won't update. 1. Improved performance of joining signals by a factor of around 5. This enables joining of 1000 and more signals in a reasonable amount of time. 1. Fixed `SignalProducer.debounce` operator that, when started more than once, would not deliver values on producers started after the first time. (#772, kudos to @gpambrozio) 1. `FlattenStrategy.throttle` is introduced. (#713, kudos to @inamiy)