diff --git a/Sources/Deprecations+Removals.swift b/Sources/Deprecations+Removals.swift index fb8790ddd..2ab5fa6b1 100644 --- a/Sources/Deprecations+Removals.swift +++ b/Sources/Deprecations+Removals.swift @@ -3,6 +3,16 @@ import Dispatch import Result // MARK: Unavailable methods in ReactiveSwift 2.0. +extension SignalProducerProtocol { + @available(*, unavailable, renamed:"init(_:)") + public static func attempt(_ operation: @escaping () -> Result) -> SignalProducer { fatalError() } +} + +extension SignalProducerProtocol where Error == AnyError { + @available(*, unavailable, renamed:"init(_:)") + public static func attempt(_ operation: @escaping () throws -> Value) -> SignalProducer { fatalError() } +} + extension PropertyProtocol { @available(*, unavailable, renamed:"flatMap(_:_:)") public func flatMap(_ strategy: FlattenStrategy, transform: @escaping (Value) -> P) -> Property { fatalError() } diff --git a/Sources/SignalProducer.swift b/Sources/SignalProducer.swift index b63b81f9f..6ce5a04cd 100644 --- a/Sources/SignalProducer.swift +++ b/Sources/SignalProducer.swift @@ -82,6 +82,26 @@ public struct SignalProducer { } } + /// Create a `SignalProducer` that will attempt the given operation once for + /// each invocation of `start()`. + /// + /// Upon success, the started signal will send the resulting value then + /// complete. Upon failure, the started signal will fail with the error that + /// occurred. + /// + /// - parameters: + /// - action: A closure that returns instance of `Result`. + public init(_ action: @escaping () -> Result) { + self.init { observer, disposable in + action().analysis(ifSuccess: { value in + observer.send(value: value) + observer.sendCompleted() + }, ifFailure: { error in + observer.send(error: error) + }) + } + } + /// Creates a producer for a `Signal` that will immediately fail with the /// given error. /// @@ -183,6 +203,25 @@ public struct SignalProducer { } } +extension SignalProducer where Error == AnyError { + /// Create a `SignalProducer` that will attempt the given failable operation once for + /// each invocation of `start()`. + /// + /// Upon success, the started producer will send the resulting value then + /// complete. Upon failure, the started signal will fail with the error that + /// occurred. + /// + /// - parameters: + /// - operation: A failable closure. + public init(_ action: @escaping () throws -> Value) { + self.init { + return ReactiveSwift.materialize { + return try action() + } + } + } +} + /// A protocol used to constraint `SignalProducer` operators. public protocol SignalProducerProtocol { /// The type of values being sent on the producer @@ -1411,58 +1450,6 @@ extension SignalProducer where Error == NoError { } } -extension SignalProducer { - /// Create a `SignalProducer` that will attempt the given operation once for - /// each invocation of `start()`. - /// - /// Upon success, the started signal will send the resulting value then - /// complete. Upon failure, the started signal will fail with the error that - /// occurred. - /// - /// - parameters: - /// - operation: A closure that returns instance of `Result`. - /// - /// - returns: A `SignalProducer` that will forward `success`ful `result` as - /// `value` event and then complete or `failed` event if `result` - /// is a `failure`. - public static func attempt(_ operation: @escaping () -> Result) -> SignalProducer { - return SignalProducer { observer, disposable in - operation().analysis(ifSuccess: { value in - observer.send(value: value) - observer.sendCompleted() - }, ifFailure: { error in - observer.send(error: error) - }) - } - } -} - -// FIXME: SWIFT_COMPILER_ISSUE -// -// One of the `SignalProducer.attempt` overloads is kept in the protocol to -// mitigate an overloading issue. Moving them back to the concrete type would be -// a binary-breaking, source-compatible change. - -extension SignalProducerProtocol where Error == AnyError { - /// Create a `SignalProducer` that, when start, would invoke a throwable action. - /// - /// The produced `Signal` would forward the result and complete if the action - /// succeeds. Otherwise, the produced signal would propagate the thrown error and - /// terminate. - /// - /// - parameters: - /// - action: A throwable closure which yields a value. - /// - /// - returns: A producer that yields the result or the error of the given action. - public static func attempt(_ action: @escaping () throws -> Value) -> SignalProducer { - return .attempt { - ReactiveSwift.materialize { - try action() - } - } - } -} - extension SignalProducer where Error == AnyError { /// Apply a throwable action to every value from `self`, and forward the values /// if the action succeeds. If the action throws an error, the produced `Signal` diff --git a/Tests/ReactiveSwiftTests/SignalProducerSpec.swift b/Tests/ReactiveSwiftTests/SignalProducerSpec.swift index fca17c35c..7a0aba729 100644 --- a/Tests/ReactiveSwiftTests/SignalProducerSpec.swift +++ b/Tests/ReactiveSwiftTests/SignalProducerSpec.swift @@ -204,7 +204,27 @@ class SignalProducerSpec: QuickSpec { } } - describe("init(_ block:)") { + describe("init closure overloading") { + it("should be inferred and overloaded without ambiguity") { + let action: () -> String = { "" } + let throwableAction: () throws -> String = { "" } + let resultAction1: () -> Result = { .success("") } + let resultAction2: () -> Result = { .success("") } + let throwableResultAction: () throws -> Result = { .success("") } + + expect(type(of: SignalProducer(action))) == SignalProducer.self + expect(type(of: SignalProducer(action))) == SignalProducer.self + expect(type(of: SignalProducer(action))) == SignalProducer.self + + expect(type(of: SignalProducer(resultAction1))) == SignalProducer.self + expect(type(of: SignalProducer(resultAction2))) == SignalProducer.self + + expect(type(of: SignalProducer(throwableAction))) == SignalProducer.self + expect(type(of: SignalProducer(throwableResultAction))) == SignalProducer, AnyError>.self + } + } + + describe("init(_:) lazy value") { it("should not evaluate the supplied closure until started") { var evaluated: Bool = false func lazyGetter() -> String { @@ -287,7 +307,7 @@ class SignalProducerSpec: QuickSpec { } } - describe("SignalProducer.attempt") { + describe("init(_:) lazy result") { it("should run the operation once per start()") { var operationRunTimes = 0 let operation: () -> Result = { @@ -296,8 +316,8 @@ class SignalProducerSpec: QuickSpec { return .success("OperationValue") } - SignalProducer.attempt(operation).start() - SignalProducer.attempt(operation).start() + SignalProducer(operation).start() + SignalProducer(operation).start() expect(operationRunTimes) == 2 } @@ -308,7 +328,7 @@ class SignalProducerSpec: QuickSpec { return .success(operationReturnValue) } - let signalProducer = SignalProducer.attempt(operation) + let signalProducer = SignalProducer(operation) expect(signalProducer).to(sendValue(operationReturnValue, sendError: nil, complete: true)) } @@ -319,20 +339,19 @@ class SignalProducerSpec: QuickSpec { return .failure(operationError) } - let signalProducer = SignalProducer.attempt(operation) + let signalProducer = SignalProducer(operation) expect(signalProducer).to(sendValue(nil, sendError: operationError, complete: false)) } } - describe("SignalProducer.attempt throws") { + describe("init(_:) throwable lazy value") { it("should send a successful value then complete") { let operationReturnValue = "OperationValue" - let signalProducer = SignalProducer - .attempt { () throws -> String in - operationReturnValue - } + let signalProducer = SignalProducer { () throws -> String in + operationReturnValue + } var error: Error? signalProducer.startWithFailed { @@ -345,10 +364,9 @@ class SignalProducerSpec: QuickSpec { it("should send the error") { let operationError = TestError.default - let signalProducer = SignalProducer - .attempt { () throws -> String in - throw operationError - } + let signalProducer = SignalProducer { () throws -> String in + throw operationError + } var error: TestError? signalProducer.startWithFailed { @@ -2691,14 +2709,14 @@ class SignalProducerSpec: QuickSpec { } } +// MARK: - Helpers + private func == (left: Expectation, right: Any.Type) { left.to(NonNilMatcherFunc { expression, _ in return try expression.evaluate()! == right }) } -// MARK: - Helpers - extension SignalProducer { internal static func pipe() -> (SignalProducer, ProducedSignal.Observer) { let (signal, observer) = ProducedSignal.pipe() @@ -2728,6 +2746,6 @@ extension SignalProducer { } } - return SignalProducer.attempt(operation) + return SignalProducer(operation) } }