diff --git a/Sources/ColdSignal.swift b/Sources/ColdSignal.swift index e72e227..acc32c3 100644 --- a/Sources/ColdSignal.swift +++ b/Sources/ColdSignal.swift @@ -11,7 +11,15 @@ import Foundation public final class ColdSignal: ColdSignalType, InternalSignalType, SpecialSignalGenerator { internal var observers = Bag>() - private let startHandler: (Observer) -> Disposable? + public var coldSignal: ColdSignal { + return self + } + + public var signal: Signal { + return self.identity + } + + internal let startHandler: (Observer) -> Disposable? private var cancelDisposable: Disposable? @@ -40,6 +48,7 @@ public final class ColdSignal: ColdSignalType, Internal /// Returns a Disposable which can be used to interrupt the work associated /// with the signal and immediately send an `Interrupted` event. + @discardableResult public func start() { if !started { started = true @@ -78,6 +87,7 @@ public final class ColdSignal: ColdSignalType, Internal /// /// Returns a Disposable which can be used to disconnect the observer. Disposing /// of the Disposable will have no effect on the Signal itself. + @discardableResult public func add(observer: Observer) -> Disposable? { let token = self.observers.insert(value: observer) return ActionDisposable { @@ -98,6 +108,9 @@ extension ColdSignal: CustomDebugStringConvertible { public protocol ColdSignalType: SignalType { + /// The exposed raw signal that underlies the ColdSignalType + var coldSignal: ColdSignal { get } + /// Invokes the closure provided upon initialization, and passes in a newly /// created observer to which events can be sent. func start() @@ -109,6 +122,23 @@ public protocol ColdSignalType: SignalType { } +extension ColdSignalType { + + /// Invokes the closure provided upon initialization, and passes in a newly + /// created observer to which events can be sent. + func start() { + coldSignal.start() + } + + /// Stops the ColdSignal by sending an interrupt to all of it's + /// observers and then invoking the disposable returned by the closure + /// that was provided upon initialization. + func stop() { + coldSignal.stop() + } + +} + extension ColdSignalType { /// Creates a ColdSignal, adds exactly one observer, and then immediately @@ -117,8 +147,8 @@ extension ColdSignalType { /// Returns a Disposable which can be used to dispose of the added observer. @discardableResult public func start(with observer: Observer) -> Disposable? { - let disposable = add(observer: observer) - start() + let disposable = coldSignal.add(observer: observer) + self.coldSignal.start() return disposable } @@ -168,3 +198,42 @@ extension ColdSignalType { } } + +extension ColdSignalType { + + public func lift(_ transform: @escaping (Signal) -> Signal) -> ColdSignal { + return ColdSignal { observer in + let (pipeSignal, pipeObserver) = Signal.pipe() + transform(pipeSignal).add(observer: observer) + return self.coldSignal.startHandler(pipeObserver) + } + } + + /// Maps each value in the signal to a new value. + /// + /// Creates a new `ColdSignal` which will apply the transform directly to events + /// produced by the `startHandler`. + /// + /// The new `ColdSignal` is in no way related to the source `ColdSignal` except + /// that they share a reference to the same `startHandler`. + public func map(_ transform: @escaping (Value) -> U) -> ColdSignal { + return lift { $0.map(transform) } + } + + /// Maps errors in the signal to a new error. + public func mapError(_ transform: @escaping (ErrorType) -> F) -> ColdSignal { + return lift { $0.mapError(transform) } + } + + /// Preserves only the values of the signal that pass the given predicate. + public func filter(_ predicate: @escaping (Value) -> Bool) -> ColdSignal { + return lift { $0.filter(predicate) } + } + + /// Aggregate values into a single combined value. Mirrors the Swift Collection + public func reduce(initial: T, _ combine: @escaping (T, Value) -> T) -> ColdSignal { + return lift { $0.reduce(initial: initial, combine) } + } + +} + diff --git a/Sources/Signal.swift b/Sources/Signal.swift index 0ba5952..e158e54 100644 --- a/Sources/Signal.swift +++ b/Sources/Signal.swift @@ -12,6 +12,10 @@ public final class Signal: SignalType, InternalSignalTy internal var observers = Bag>() + public var signal: Signal { + return self + } + /// Initializes a Signal that will immediately invoke the given generator, /// then forward events sent to the given observer. /// @@ -42,6 +46,7 @@ public final class Signal: SignalType, InternalSignalTy /// /// Returns a Disposable which can be used to disconnect the observer. Disposing /// of the Disposable will have no effect on the Signal itself. + @discardableResult public func add(observer: Observer) -> Disposable? { let token = observers.insert(value: observer) return ActionDisposable { [weak self] in @@ -159,8 +164,11 @@ public protocol SignalType { associatedtype ErrorType: Error /// Observes the Signal by sending any future events to the given observer. + @discardableResult func add(observer: Observer) -> Disposable? + /// The exposed raw signal that underlies the ColdSignalType + var signal: Signal { get } } @@ -189,7 +197,7 @@ extension SignalType { /// invocations. @discardableResult public func on(action: @escaping Observer.Action) -> Disposable? { - return add(observer: Observer(action)) + return signal.add(observer: Observer(action)) } /// Observes the Signal by invoking the given callback when `next` events are @@ -200,7 +208,7 @@ extension SignalType { /// itself. @discardableResult public func onNext(next: @escaping (Value) -> Void) -> Disposable? { - return add(observer: Observer(next: next)) + return signal.add(observer: Observer(next: next)) } /// Observes the Signal by invoking the given callback when a `completed` event is @@ -211,7 +219,7 @@ extension SignalType { /// itself. @discardableResult public func onCompleted(completed: @escaping () -> Void) -> Disposable? { - return add(observer: Observer(completed: completed)) + return signal.add(observer: Observer(completed: completed)) } /// Observes the Signal by invoking the given callback when a `failed` event is @@ -222,7 +230,7 @@ extension SignalType { /// itself. @discardableResult public func onFailed(error: @escaping (ErrorType) -> Void) -> Disposable? { - return add(observer: Observer(failed: error)) + return signal.add(observer: Observer(failed: error)) } /// Observes the Signal by invoking the given callback when an `interrupted` event is @@ -234,7 +242,15 @@ extension SignalType { /// itself. @discardableResult public func onInterrupted(interrupted: @escaping () -> Void) -> Disposable? { - return add(observer: Observer(interrupted: interrupted)) + return signal.add(observer: Observer(interrupted: interrupted)) + } + +} + +extension SignalType { + + public var identity: Signal { + return self.map { $0 } } /// Maps each value in the signal to a new value.