Skip to content

Commit 9030845

Browse files
authored
Merge pull request #128 from inamiy/feature/sample-from
Add withLatest(from:) (Rx.withLatestFrom)
2 parents 02e7cf6 + 9580123 commit 9030845

File tree

4 files changed

+376
-0
lines changed

4 files changed

+376
-0
lines changed

Sources/Signal.swift

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,6 +1026,77 @@ extension SignalProtocol {
10261026
.map { $0.0 }
10271027
}
10281028

1029+
/// Forward the latest value from `samplee` with the value from `self` as a
1030+
/// tuple, only when `self` sends a `value` event.
1031+
/// This is like a flipped version of `sample(with:)`, but `samplee`'s
1032+
/// terminal events are completely ignored.
1033+
///
1034+
/// - note: If `self` fires before a value has been observed on `samplee`,
1035+
/// nothing happens.
1036+
///
1037+
/// - parameters:
1038+
/// - samplee: A signal whose latest value is sampled by `self`.
1039+
///
1040+
/// - returns: A signal that will send values from `self` and `samplee`,
1041+
/// sampled (possibly multiple times) by `self`, then terminate
1042+
/// once `self` has terminated. **`samplee`'s terminated events
1043+
/// are ignored**.
1044+
public func withLatest<U>(from samplee: Signal<U, NoError>) -> Signal<(Value, U), Error> {
1045+
return Signal { observer in
1046+
let state = Atomic<U?>(nil)
1047+
let disposable = CompositeDisposable()
1048+
1049+
disposable += samplee.observeValues { value in
1050+
state.value = value
1051+
}
1052+
1053+
disposable += self.observe { event in
1054+
switch event {
1055+
case let .value(value):
1056+
if let value2 = state.value {
1057+
observer.send(value: (value, value2))
1058+
}
1059+
case .completed:
1060+
observer.sendCompleted()
1061+
case let .failed(error):
1062+
observer.send(error: error)
1063+
case .interrupted:
1064+
observer.sendInterrupted()
1065+
}
1066+
}
1067+
1068+
return disposable
1069+
}
1070+
}
1071+
1072+
/// Forward the latest value from `samplee` with the value from `self` as a
1073+
/// tuple, only when `self` sends a `value` event.
1074+
/// This is like a flipped version of `sample(with:)`, but `samplee`'s
1075+
/// terminal events are completely ignored.
1076+
///
1077+
/// - note: If `self` fires before a value has been observed on `samplee`,
1078+
/// nothing happens.
1079+
///
1080+
/// - parameters:
1081+
/// - samplee: A producer whose latest value is sampled by `self`.
1082+
///
1083+
/// - returns: A signal that will send values from `self` and `samplee`,
1084+
/// sampled (possibly multiple times) by `self`, then terminate
1085+
/// once `self` has terminated. **`samplee`'s terminated events
1086+
/// are ignored**.
1087+
public func withLatest<U>(from samplee: SignalProducer<U, NoError>) -> Signal<(Value, U), Error> {
1088+
return Signal { observer in
1089+
let d = CompositeDisposable()
1090+
samplee.startWithSignal { signal, disposable in
1091+
d += disposable
1092+
d += self.withLatest(from: signal).observe(observer)
1093+
}
1094+
return d
1095+
}
1096+
}
1097+
}
1098+
1099+
extension SignalProtocol {
10291100
/// Forwards events from `self` until `lifetime` ends, at which point the
10301101
/// returned signal will complete.
10311102
///

Sources/SignalProducer.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -780,6 +780,44 @@ extension SignalProducerProtocol {
780780
return lift(Signal.sample(on:))(sampler)
781781
}
782782

783+
/// Forward the latest value from `samplee` with the value from `self` as a
784+
/// tuple, only when `self` sends a `value` event.
785+
/// This is like a flipped version of `sample(with:)`, but `samplee`'s
786+
/// terminal events are completely ignored.
787+
///
788+
/// - note: If `self` fires before a value has been observed on `samplee`,
789+
/// nothing happens.
790+
///
791+
/// - parameters:
792+
/// - samplee: A producer whose latest value is sampled by `self`.
793+
///
794+
/// - returns: A signal that will send values from `self` and `samplee`,
795+
/// sampled (possibly multiple times) by `self`, then terminate
796+
/// once `self` has terminated. **`samplee`'s terminated events
797+
/// are ignored**.
798+
public func withLatest<U>(from samplee: SignalProducer<U, NoError>) -> SignalProducer<(Value, U), Error> {
799+
return liftRight(Signal.withLatest)(samplee)
800+
}
801+
802+
/// Forward the latest value from `samplee` with the value from `self` as a
803+
/// tuple, only when `self` sends a `value` event.
804+
/// This is like a flipped version of `sample(with:)`, but `samplee`'s
805+
/// terminal events are completely ignored.
806+
///
807+
/// - note: If `self` fires before a value has been observed on `samplee`,
808+
/// nothing happens.
809+
///
810+
/// - parameters:
811+
/// - samplee: A signal whose latest value is sampled by `self`.
812+
///
813+
/// - returns: A signal that will send values from `self` and `samplee`,
814+
/// sampled (possibly multiple times) by `self`, then terminate
815+
/// once `self` has terminated. **`samplee`'s terminated events
816+
/// are ignored**.
817+
public func withLatest<U>(from samplee: Signal<U, NoError>) -> SignalProducer<(Value, U), Error> {
818+
return lift(Signal.withLatest)(samplee)
819+
}
820+
783821
/// Forwards events from `self` until `lifetime` ends, at which point the
784822
/// returned producer will complete.
785823
///

Tests/ReactiveSwiftTests/SignalProducerLiftingSpec.swift

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,6 +1114,138 @@ class SignalProducerLiftingSpec: QuickSpec {
11141114
}
11151115
}
11161116

1117+
describe("withLatest(from: signal)") {
1118+
var withLatestProducer: SignalProducer<(Int, String), NoError>!
1119+
var observer: Signal<Int, NoError>.Observer!
1120+
var sampleeObserver: Signal<String, NoError>.Observer!
1121+
1122+
beforeEach {
1123+
let (producer, incomingObserver) = SignalProducer<Int, NoError>.pipe()
1124+
let (samplee, incomingSampleeObserver) = Signal<String, NoError>.pipe()
1125+
withLatestProducer = producer.withLatest(from: samplee)
1126+
observer = incomingObserver
1127+
sampleeObserver = incomingSampleeObserver
1128+
}
1129+
1130+
it("should forward the latest value when the receiver fires") {
1131+
var result: [String] = []
1132+
withLatestProducer.startWithValues { (left, right) in result.append("\(left)\(right)") }
1133+
1134+
sampleeObserver.send(value: "a")
1135+
sampleeObserver.send(value: "b")
1136+
observer.send(value: 1)
1137+
expect(result) == [ "1b" ]
1138+
}
1139+
1140+
it("should do nothing if receiver fires before samplee sends value") {
1141+
var result: [String] = []
1142+
withLatestProducer.startWithValues { (left, right) in result.append("\(left)\(right)") }
1143+
1144+
observer.send(value: 1)
1145+
expect(result).to(beEmpty())
1146+
}
1147+
1148+
it("should send latest value with samplee value multiple times when receiver fires multiple times") {
1149+
var result: [String] = []
1150+
withLatestProducer.startWithValues { (left, right) in result.append("\(left)\(right)") }
1151+
1152+
sampleeObserver.send(value: "a")
1153+
observer.send(value: 1)
1154+
observer.send(value: 2)
1155+
expect(result) == [ "1a", "2a" ]
1156+
}
1157+
1158+
it("should complete when receiver has completed") {
1159+
var completed = false
1160+
withLatestProducer.startWithCompleted { completed = true }
1161+
1162+
observer.sendCompleted()
1163+
expect(completed) == true
1164+
}
1165+
1166+
it("should not affect when samplee has completed") {
1167+
var event: Event<(Int, String), NoError>? = nil
1168+
withLatestProducer.start { event = $0 }
1169+
1170+
sampleeObserver.sendCompleted()
1171+
expect(event).to(beNil())
1172+
}
1173+
1174+
it("should not affect when samplee has interrupted") {
1175+
var event: Event<(Int, String), NoError>? = nil
1176+
withLatestProducer.start { event = $0 }
1177+
1178+
sampleeObserver.sendInterrupted()
1179+
expect(event).to(beNil())
1180+
}
1181+
}
1182+
1183+
describe("withLatest(from: producer)") {
1184+
var withLatestProducer: SignalProducer<(Int, String), NoError>!
1185+
var observer: Signal<Int, NoError>.Observer!
1186+
var sampleeObserver: Signal<String, NoError>.Observer!
1187+
1188+
beforeEach {
1189+
let (producer, incomingObserver) = SignalProducer<Int, NoError>.pipe()
1190+
let (samplee, incomingSampleeObserver) = SignalProducer<String, NoError>.pipe()
1191+
withLatestProducer = producer.withLatest(from: samplee)
1192+
observer = incomingObserver
1193+
sampleeObserver = incomingSampleeObserver
1194+
}
1195+
1196+
it("should forward the latest value when the receiver fires") {
1197+
var result: [String] = []
1198+
withLatestProducer.startWithValues { (left, right) in result.append("\(left)\(right)") }
1199+
1200+
sampleeObserver.send(value: "a")
1201+
sampleeObserver.send(value: "b")
1202+
observer.send(value: 1)
1203+
expect(result) == [ "1b" ]
1204+
}
1205+
1206+
it("should do nothing if receiver fires before samplee sends value") {
1207+
var result: [String] = []
1208+
withLatestProducer.startWithValues { (left, right) in result.append("\(left)\(right)") }
1209+
1210+
observer.send(value: 1)
1211+
expect(result).to(beEmpty())
1212+
}
1213+
1214+
it("should send latest value with samplee value multiple times when receiver fires multiple times") {
1215+
var result: [String] = []
1216+
withLatestProducer.startWithValues { (left, right) in result.append("\(left)\(right)") }
1217+
1218+
sampleeObserver.send(value: "a")
1219+
observer.send(value: 1)
1220+
observer.send(value: 2)
1221+
expect(result) == [ "1a", "2a" ]
1222+
}
1223+
1224+
it("should complete when receiver has completed") {
1225+
var completed = false
1226+
withLatestProducer.startWithCompleted { completed = true }
1227+
1228+
observer.sendCompleted()
1229+
expect(completed) == true
1230+
}
1231+
1232+
it("should not affect when samplee has completed") {
1233+
var event: Event<(Int, String), NoError>? = nil
1234+
withLatestProducer.start { event = $0 }
1235+
1236+
sampleeObserver.sendCompleted()
1237+
expect(event).to(beNil())
1238+
}
1239+
1240+
it("should not affect when samplee has interrupted") {
1241+
var event: Event<(Int, String), NoError>? = nil
1242+
withLatestProducer.start { event = $0 }
1243+
1244+
sampleeObserver.sendInterrupted()
1245+
expect(event).to(beNil())
1246+
}
1247+
}
1248+
11171249
describe("combineLatestWith") {
11181250
var combinedProducer: SignalProducer<(Int, Double), NoError>!
11191251
var observer: Signal<Int, NoError>.Observer!

0 commit comments

Comments
 (0)