From 34d645000b68a22c5b06897917a127d6d4bf1bb9 Mon Sep 17 00:00:00 2001 From: Andrew Breckenridge Date: Wed, 24 Apr 2019 10:51:11 -0700 Subject: [PATCH 1/2] Introduce MergeSignals.swift with merge & combineLatest --- Signals.xcodeproj/project.pbxproj | 10 +++++ Signals/MergeSignal.swift | 72 +++++++++++++++++++++++++++++++ Signals/Signal.swift | 2 +- 3 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 Signals/MergeSignal.swift diff --git a/Signals.xcodeproj/project.pbxproj b/Signals.xcodeproj/project.pbxproj index f99e625..9bc9361 100644 --- a/Signals.xcodeproj/project.pbxproj +++ b/Signals.xcodeproj/project.pbxproj @@ -8,6 +8,10 @@ /* Begin PBXBuildFile section */ 158A3F152052EE8E00174952 /* UIBarButtonItem+Signals.swift in Sources */ = {isa = PBXBuildFile; fileRef = 158A3F142052EE8E00174952 /* UIBarButtonItem+Signals.swift */; }; + 46874B202270D79000162049 /* MergeSignal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46874B1F2270D79000162049 /* MergeSignal.swift */; }; + 46874B212270D79000162049 /* MergeSignal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46874B1F2270D79000162049 /* MergeSignal.swift */; }; + 46874B222270D79000162049 /* MergeSignal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46874B1F2270D79000162049 /* MergeSignal.swift */; }; + 46874B232270D79000162049 /* MergeSignal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46874B1F2270D79000162049 /* MergeSignal.swift */; }; 720D11781A085315003C4361 /* Signals.h in Headers */ = {isa = PBXBuildFile; fileRef = 720D11771A085315003C4361 /* Signals.h */; settings = {ATTRIBUTES = (Public, ); }; }; 720D117E1A085315003C4361 /* Signals.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 720D11721A085314003C4361 /* Signals.framework */; }; 720D11851A085315003C4361 /* SignalsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 720D11841A085315003C4361 /* SignalsTests.swift */; }; @@ -65,6 +69,7 @@ /* Begin PBXFileReference section */ 158A3F142052EE8E00174952 /* UIBarButtonItem+Signals.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "UIBarButtonItem+Signals.swift"; path = "ios/UIBarButtonItem+Signals.swift"; sourceTree = ""; }; + 46874B1F2270D79000162049 /* MergeSignal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MergeSignal.swift; sourceTree = ""; }; 720D11721A085314003C4361 /* Signals.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Signals.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 720D11761A085315003C4361 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 720D11771A085315003C4361 /* Signals.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Signals.h; sourceTree = ""; }; @@ -171,6 +176,7 @@ children = ( 720D11771A085315003C4361 /* Signals.h */, 720D11941A08537E003C4361 /* Signal.swift */, + 46874B1F2270D79000162049 /* MergeSignal.swift */, 724993511C435569006653CB /* iOS */, 720D11751A085315003C4361 /* Supporting Files */, ); @@ -504,6 +510,7 @@ files = ( 72321EFC1DB74CA300E79E9D /* AssociatedObject.swift in Sources */, 72321EFF1DB74CBF00E79E9D /* UIControl+Signals.swift in Sources */, + 46874B202270D79000162049 /* MergeSignal.swift in Sources */, 158A3F152052EE8E00174952 /* UIBarButtonItem+Signals.swift in Sources */, 720D11951A08537E003C4361 /* Signal.swift in Sources */, ); @@ -525,6 +532,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 46874B232270D79000162049 /* MergeSignal.swift in Sources */, 721E12A91C1E8E1D00CD0CBA /* Signal.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -535,6 +543,7 @@ files = ( 72321EFD1DB74CA400E79E9D /* AssociatedObject.swift in Sources */, 72321F001DB74CC000E79E9D /* UIControl+Signals.swift in Sources */, + 46874B222270D79000162049 /* MergeSignal.swift in Sources */, 725B76CD1C26572E001D04CC /* Signal.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -555,6 +564,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 46874B212270D79000162049 /* MergeSignal.swift in Sources */, 72665B4E1C212BEC0067DF26 /* Signal.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Signals/MergeSignal.swift b/Signals/MergeSignal.swift new file mode 100644 index 0000000..c2d94fb --- /dev/null +++ b/Signals/MergeSignal.swift @@ -0,0 +1,72 @@ +import Foundation +#if os(Linux) +import Dispatch +#endif + +public extension Signal { + /// http://rxmarbles.com/#merge + static func merge(_ signalOne: Signal, _ signalTwo: Signal, retainLastData: Bool = true) -> Signal<(T?, U?)> { + return MergeSignals(signalOne, signalTwo) + } + + /// http://rxmarbles.com/#combineLatest + static func combineLatest(_ signalOne: Signal, _ signalTwo: Signal, retainLatestData: Bool = true) -> Signal<(T, U)> { + return CombineLatestSignals(signalOne, signalTwo) + } +} + +private class CombineLatestSignals: Signal<(A, B)> { + private var oneLastData: A? = .none + private var twoLastData: B? = .none + + let signalOne: Signal + let signalTwo: Signal + + init(_ signalOne: Signal, _ signalTwo: Signal, retainLastData: Bool = true) { + self.signalOne = signalOne + self.signalTwo = signalTwo + super.init(retainLastData: retainLastData) + + signalOne.subscribePast(with: self) { a in + self.oneLastData = a + self.forwardToCombinedIfAppropriate() + } + signalTwo.subscribePast(with: self) { b in + self.twoLastData = b + self.forwardToCombinedIfAppropriate() + } + } + + private func forwardToCombinedIfAppropriate() { + guard let oneData = oneLastData, let twoData = twoLastData else { return } + + self.fire((oneData, twoData)) + } +} + +private class MergeSignals: Signal<(A?, B?)> { + private var oneLastData: A? = .none + private var twoLastData: B? = .none + + let signalOne: Signal + let signalTwo: Signal + + init(_ signalOne: Signal, _ signalTwo: Signal, retainLastData: Bool = true) { + self.signalOne = signalOne + self.signalTwo = signalTwo + super.init(retainLastData: retainLastData) + + signalOne.subscribePast(with: self) { a in + self.oneLastData = a + self.forwardSignal() + } + signalTwo.subscribePast(with: self) { b in + self.twoLastData = b + self.forwardSignal() + } + } + + private func forwardSignal() { + self.fire((oneLastData, twoLastData)) + } +} diff --git a/Signals/Signal.swift b/Signals/Signal.swift index 9806c90..c35faa6 100644 --- a/Signals/Signal.swift +++ b/Signals/Signal.swift @@ -9,7 +9,7 @@ import Dispatch /// Create instances of `Signal` and assign them to public constants on your class for each event type that your /// class fires. -final public class Signal { +public class Signal { public typealias SignalCallback = (T) -> Void From 52a920d6c0c252c3b9dbe02c78f3027d5e4274f4 Mon Sep 17 00:00:00 2001 From: Andrew Breckenridge Date: Wed, 24 Apr 2019 11:01:32 -0700 Subject: [PATCH 2/2] Add option with 3 signals to merge & combineLatest Maybe it would have been nice to use .tt or .gyb, but it would have taken longer. So just manual for now. Also, it might be useful to have a 4 & 5 variant. --- Signals.xcodeproj/project.pbxproj | 10 ++++ Signals/MergeSignal.swift | 2 +- Signals/MergeSignals+arity.swift | 88 +++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 Signals/MergeSignals+arity.swift diff --git a/Signals.xcodeproj/project.pbxproj b/Signals.xcodeproj/project.pbxproj index 9bc9361..192946b 100644 --- a/Signals.xcodeproj/project.pbxproj +++ b/Signals.xcodeproj/project.pbxproj @@ -12,6 +12,10 @@ 46874B212270D79000162049 /* MergeSignal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46874B1F2270D79000162049 /* MergeSignal.swift */; }; 46874B222270D79000162049 /* MergeSignal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46874B1F2270D79000162049 /* MergeSignal.swift */; }; 46874B232270D79000162049 /* MergeSignal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46874B1F2270D79000162049 /* MergeSignal.swift */; }; + 46874B252270D9BF00162049 /* MergeSignals+arity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46874B242270D9BF00162049 /* MergeSignals+arity.swift */; }; + 46874B262270D9BF00162049 /* MergeSignals+arity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46874B242270D9BF00162049 /* MergeSignals+arity.swift */; }; + 46874B272270D9BF00162049 /* MergeSignals+arity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46874B242270D9BF00162049 /* MergeSignals+arity.swift */; }; + 46874B282270D9BF00162049 /* MergeSignals+arity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46874B242270D9BF00162049 /* MergeSignals+arity.swift */; }; 720D11781A085315003C4361 /* Signals.h in Headers */ = {isa = PBXBuildFile; fileRef = 720D11771A085315003C4361 /* Signals.h */; settings = {ATTRIBUTES = (Public, ); }; }; 720D117E1A085315003C4361 /* Signals.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 720D11721A085314003C4361 /* Signals.framework */; }; 720D11851A085315003C4361 /* SignalsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 720D11841A085315003C4361 /* SignalsTests.swift */; }; @@ -70,6 +74,7 @@ /* Begin PBXFileReference section */ 158A3F142052EE8E00174952 /* UIBarButtonItem+Signals.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "UIBarButtonItem+Signals.swift"; path = "ios/UIBarButtonItem+Signals.swift"; sourceTree = ""; }; 46874B1F2270D79000162049 /* MergeSignal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MergeSignal.swift; sourceTree = ""; }; + 46874B242270D9BF00162049 /* MergeSignals+arity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MergeSignals+arity.swift"; sourceTree = ""; }; 720D11721A085314003C4361 /* Signals.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Signals.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 720D11761A085315003C4361 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 720D11771A085315003C4361 /* Signals.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Signals.h; sourceTree = ""; }; @@ -177,6 +182,7 @@ 720D11771A085315003C4361 /* Signals.h */, 720D11941A08537E003C4361 /* Signal.swift */, 46874B1F2270D79000162049 /* MergeSignal.swift */, + 46874B242270D9BF00162049 /* MergeSignals+arity.swift */, 724993511C435569006653CB /* iOS */, 720D11751A085315003C4361 /* Supporting Files */, ); @@ -508,6 +514,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 46874B252270D9BF00162049 /* MergeSignals+arity.swift in Sources */, 72321EFC1DB74CA300E79E9D /* AssociatedObject.swift in Sources */, 72321EFF1DB74CBF00E79E9D /* UIControl+Signals.swift in Sources */, 46874B202270D79000162049 /* MergeSignal.swift in Sources */, @@ -532,6 +539,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 46874B282270D9BF00162049 /* MergeSignals+arity.swift in Sources */, 46874B232270D79000162049 /* MergeSignal.swift in Sources */, 721E12A91C1E8E1D00CD0CBA /* Signal.swift in Sources */, ); @@ -542,6 +550,7 @@ buildActionMask = 2147483647; files = ( 72321EFD1DB74CA400E79E9D /* AssociatedObject.swift in Sources */, + 46874B272270D9BF00162049 /* MergeSignals+arity.swift in Sources */, 72321F001DB74CC000E79E9D /* UIControl+Signals.swift in Sources */, 46874B222270D79000162049 /* MergeSignal.swift in Sources */, 725B76CD1C26572E001D04CC /* Signal.swift in Sources */, @@ -564,6 +573,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 46874B262270D9BF00162049 /* MergeSignals+arity.swift in Sources */, 46874B212270D79000162049 /* MergeSignal.swift in Sources */, 72665B4E1C212BEC0067DF26 /* Signal.swift in Sources */, ); diff --git a/Signals/MergeSignal.swift b/Signals/MergeSignal.swift index c2d94fb..acf1d94 100644 --- a/Signals/MergeSignal.swift +++ b/Signals/MergeSignal.swift @@ -37,7 +37,7 @@ private class CombineLatestSignals: Signal<(A, B)> { } } - private func forwardToCombinedIfAppropriate() { + internal func forwardToCombinedIfAppropriate() { guard let oneData = oneLastData, let twoData = twoLastData else { return } self.fire((oneData, twoData)) diff --git a/Signals/MergeSignals+arity.swift b/Signals/MergeSignals+arity.swift new file mode 100644 index 0000000..182ec4c --- /dev/null +++ b/Signals/MergeSignals+arity.swift @@ -0,0 +1,88 @@ +import Foundation +#if os(Linux) +import Dispatch +#endif + +public extension Signal { + /// http://rxmarbles.com/#merge + static func merge(_ signalOne: Signal, _ signalTwo: Signal, _ signalThree: Signal, retainLastData: Bool = true) -> Signal<(T?, U?, V?)> { + return MergeSignals3(signalOne, signalTwo, signalThree) + } + + /// http://rxmarbles.com/#combineLatest + static func combineLatest(_ signalOne: Signal, _ signalTwo: Signal, _ signalThree: Signal, retainLatestData: Bool = true) -> Signal<(T, U, V)> { + return CombineLatestSignals3(signalOne, signalTwo, signalThree) + } +} + +private class MergeSignals3: Signal<(A?, B?, C?)> { + private var oneLastData: A? + private var twoLastData: B? + private var threeLastData: C? + + let signalOne: Signal + let signalTwo: Signal + let signalThree: Signal + + init(_ signalOne: Signal, _ signalTwo: Signal, _ signalThree: Signal, retainLastData: Bool = true) { + self.signalOne = signalOne + self.signalTwo = signalTwo + self.signalThree = signalThree + super.init(retainLastData: retainLastData) + + signalOne.subscribePast(with: self) { a in + self.oneLastData = a + self.forwardSignal() + } + signalTwo.subscribePast(with: self) { b in + self.twoLastData = b + self.forwardSignal() + } + signalThree.subscribe(with: self) { c in + self.threeLastData = c + self.forwardSignal() + } + } + + private func forwardSignal() { + self.fire((oneLastData, twoLastData, threeLastData)) + } +} + +private class CombineLatestSignals3: Signal<(A, B, C)> { + private var oneLastData: A? + private var twoLastData: B? + private var threeLastData: C? + + let signalOne: Signal + let signalTwo: Signal + let signalThree: Signal + + init(_ signalOne: Signal, _ signalTwo: Signal, _ signalThree: Signal, retainLastData: Bool = true) { + self.signalOne = signalOne + self.signalTwo = signalTwo + self.signalThree = signalThree + super.init(retainLastData: retainLastData) + + signalOne.subscribePast(with: self) { a in + self.oneLastData = a + self.forwardToCombinedIfAppropriate() + } + signalTwo.subscribePast(with: self) { b in + self.twoLastData = b + self.forwardToCombinedIfAppropriate() + } + signalThree.subscribe(with: self) { c in + self.threeLastData = c + self.forwardToCombinedIfAppropriate() + } + } + + internal func forwardToCombinedIfAppropriate() { + guard let oneData = oneLastData, + let twoData = twoLastData, + let threeData = threeLastData else { return } + + self.fire((oneData, twoData, threeData)) + } +}