diff --git a/CHANGELOG.md b/CHANGELOG.md index 26b01f0e1..22d85e861 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ All notable changes to this project will be documented in this file. --- +## 6.0.0-beta.1 + +**Note**: RxSwift 6 and above has a minimum target of Swift 5.1 (Xcode 11) + +* Remove `UIWebView` Reactive Extensions due to Apple hard deprecation. #2062 +* Minimum Swift version is now 5.1. #2077 +* Remove scoped imports in favor of library evolution. #2103 +* Add `Driver.drive()` and `Signal.emit()` for multiple observers/relays. #1962 +* Add `compactMap` to `SharedSequence`, `Single` and `Maybe`. #1978 +* Add `UITextField.isSecureTextEntry` binder. #1968 +* Remove "custom" `Result` in favor of `Foundation.Resault`. #2006 +* Fix compilation error in `SharedSequence.createUnsafe`. #2014 + ## [5.0.1](https://github.com/ReactiveX/RxSwift/releases/tag/5.0.1) * Reverts Carthage integration from using static to dynamic libraries. #1960 diff --git a/RxCocoa/Common/Observable+Bind.swift b/RxCocoa/Common/Observable+Bind.swift index cd87b344a..a6acc3980 100644 --- a/RxCocoa/Common/Observable+Bind.swift +++ b/RxCocoa/Common/Observable+Bind.swift @@ -17,7 +17,9 @@ extension ObservableType { - returns: Disposable object that can be used to unsubscribe the observers. */ public func bind(to observers: Observer...) -> Disposable where Observer.Element == Element { - return self.bind(to: observers) + return self.subscribe { event in + observers.forEach { $0.on(event) } + } } /** @@ -28,20 +30,10 @@ extension ObservableType { - returns: Disposable object that can be used to unsubscribe the observers. */ public func bind(to observers: Observer...) -> Disposable where Observer.Element == Element? { - self.map { $0 as Element? }.bind(to: observers) - } - - /** - Creates new subscription and sends elements to observer(s). - In this form, it's equivalent to the `subscribe` method, but it better conveys intent, and enables - writing more consistent binding code. - - parameter to: Observers to receives events. - - returns: Disposable object that can be used to unsubscribe the observers. - */ - private func bind(to observers: [Observer]) -> Disposable where Observer.Element == Element { - self.subscribe { event in - observers.forEach { $0.on(event) } - } + return self.map { $0 as Element? } + .subscribe { event in + observers.forEach { $0.on(event) } + } } /** diff --git a/RxCocoa/Traits/Driver/Driver+Subscription.swift b/RxCocoa/Traits/Driver/Driver+Subscription.swift index 902674720..6ed117983 100644 --- a/RxCocoa/Traits/Driver/Driver+Subscription.swift +++ b/RxCocoa/Traits/Driver/Driver+Subscription.swift @@ -22,9 +22,13 @@ extension SharedSequenceConvertibleType where SharingStrategy == DriverSharingSt - parameter observer: Observer that receives events. - returns: Disposable object that can be used to unsubscribe the observer from the subject. */ - public func drive(_ observer: Observer) -> Disposable where Observer.Element == Element { + public func drive(_ observers: Observer...) -> Disposable where Observer.Element == Element { MainScheduler.ensureRunningOnMainThread(errorMessage: errorMessage) - return self.asSharedSequence().asObservable().subscribe(observer) + return self.asSharedSequence() + .asObservable() + .subscribe { e in + observers.forEach { $0.on(e) } + } } /** @@ -36,9 +40,14 @@ extension SharedSequenceConvertibleType where SharingStrategy == DriverSharingSt - parameter observer: Observer that receives events. - returns: Disposable object that can be used to unsubscribe the observer from the subject. */ - public func drive(_ observer: Observer) -> Disposable where Observer.Element == Element? { + public func drive(_ observers: Observer...) -> Disposable where Observer.Element == Element? { MainScheduler.ensureRunningOnMainThread(errorMessage: errorMessage) - return self.asSharedSequence().asObservable().map { $0 as Element? }.subscribe(observer) + return self.asSharedSequence() + .asObservable() + .map { $0 as Element? } + .subscribe { e in + observers.forEach { $0.on(e) } + } } /** @@ -48,10 +57,10 @@ extension SharedSequenceConvertibleType where SharingStrategy == DriverSharingSt - parameter relay: Target relay for sequence elements. - returns: Disposable object that can be used to unsubscribe the observer from the relay. */ - public func drive(_ relay: BehaviorRelay) -> Disposable { + public func drive(_ relays: BehaviorRelay...) -> Disposable { MainScheduler.ensureRunningOnMainThread(errorMessage: errorMessage) return self.drive(onNext: { e in - relay.accept(e) + relays.forEach { $0.accept(e) } }) } @@ -62,10 +71,10 @@ extension SharedSequenceConvertibleType where SharingStrategy == DriverSharingSt - parameter relay: Target relay for sequence elements. - returns: Disposable object that can be used to unsubscribe the observer from the relay. */ - public func drive(_ relay: BehaviorRelay) -> Disposable { + public func drive(_ relays: BehaviorRelay...) -> Disposable { MainScheduler.ensureRunningOnMainThread(errorMessage: errorMessage) return self.drive(onNext: { e in - relay.accept(e) + relays.forEach { $0.accept(e) } }) } diff --git a/RxCocoa/Traits/Signal/Signal+Subscription.swift b/RxCocoa/Traits/Signal/Signal+Subscription.swift index b9bd7aded..1daff7c41 100644 --- a/RxCocoa/Traits/Signal/Signal+Subscription.swift +++ b/RxCocoa/Traits/Signal/Signal+Subscription.swift @@ -15,11 +15,15 @@ extension SharedSequenceConvertibleType where SharingStrategy == SignalSharingSt In this form it's equivalent to `subscribe` method, but it communicates intent better. - - parameter to: Observer that receives events. + - parameter to: Observers that receives events. - returns: Disposable object that can be used to unsubscribe the observer from the subject. */ - public func emit(to observer: Observer) -> Disposable where Observer.Element == Element { - return self.asSharedSequence().asObservable().subscribe(observer) + public func emit(to observers: Observer...) -> Disposable where Observer.Element == Element { + return self.asSharedSequence() + .asObservable() + .subscribe { event in + observers.forEach { $0.on(event) } + } } /** @@ -27,44 +31,49 @@ extension SharedSequenceConvertibleType where SharingStrategy == SignalSharingSt In this form it's equivalent to `subscribe` method, but it communicates intent better. - - parameter to: Observer that receives events. + - parameter to: Observers that receives events. - returns: Disposable object that can be used to unsubscribe the observer from the subject. */ - public func emit(to observer: Observer) -> Disposable where Observer.Element == Element? { - self.asSharedSequence().asObservable().map { $0 as Element? }.subscribe(observer) + public func emit(to observers: Observer...) -> Disposable where Observer.Element == Element? { + return self.asSharedSequence() + .asObservable() + .map { $0 as Element? } + .subscribe { event in + observers.forEach { $0.on(event) } + } } /** Creates new subscription and sends elements to `BehaviorRelay`. - - parameter relay: Target relay for sequence elements. + - parameter to: Target relays for sequence elements. - returns: Disposable object that can be used to unsubscribe the observer from the relay. */ - public func emit(to relay: BehaviorRelay) -> Disposable { - self.emit(onNext: { e in - relay.accept(e) + public func emit(to relays: BehaviorRelay...) -> Disposable { + return self.emit(onNext: { e in + relays.forEach { $0.accept(e) } }) } /** Creates new subscription and sends elements to `BehaviorRelay`. - - parameter relay: Target relay for sequence elements. + - parameter to: Target relays for sequence elements. - returns: Disposable object that can be used to unsubscribe the observer from the relay. */ - public func emit(to relay: BehaviorRelay) -> Disposable { - self.emit(onNext: { e in - relay.accept(e) + public func emit(to relays: BehaviorRelay...) -> Disposable { + return self.emit(onNext: { e in + relays.forEach { $0.accept(e) } }) } /** Creates new subscription and sends elements to relay. - - parameter relay: Target relay for sequence elements. + - parameter to: Target relays for sequence elements. - returns: Disposable object that can be used to unsubscribe the observer from the relay. */ - public func emit(to relay: PublishRelay) -> Disposable { - self.emit(onNext: { e in - relay.accept(e) + public func emit(to relays: PublishRelay...) -> Disposable { + return self.emit(onNext: { e in + relays.forEach { $0.accept(e) } }) } @@ -74,9 +83,9 @@ extension SharedSequenceConvertibleType where SharingStrategy == SignalSharingSt - parameter to: Target relay for sequence elements. - returns: Disposable object that can be used to unsubscribe the observer from the relay. */ - public func emit(to relay: PublishRelay) -> Disposable { - self.emit(onNext: { e in - relay.accept(e) + public func emit(to relays: PublishRelay...) -> Disposable { + return self.emit(onNext: { e in + relays.forEach { $0.accept(e) } }) } diff --git a/Sources/AllTestz/main.swift b/Sources/AllTestz/main.swift index 273fe4475..59cba32b4 100644 --- a/Sources/AllTestz/main.swift +++ b/Sources/AllTestz/main.swift @@ -238,16 +238,17 @@ final class DriverTest_ : DriverTest, RxTestCase { ("testDrivingOrderOfSynchronousSubscriptions1", DriverTest.testDrivingOrderOfSynchronousSubscriptions1), ("testDrivingOrderOfSynchronousSubscriptions2", DriverTest.testDrivingOrderOfSynchronousSubscriptions2), ("testDriveObserver", DriverTest.testDriveObserver), + ("testDriveObservers", DriverTest.testDriveObservers), ("testDriveOptionalObserver", DriverTest.testDriveOptionalObserver), + ("testDriveOptionalObservers", DriverTest.testDriveOptionalObservers), ("testDriveNoAmbiguity", DriverTest.testDriveNoAmbiguity), ("testDriveRelay", DriverTest.testDriveRelay), + ("testDriveRelays", DriverTest.testDriveRelays), ("testDriveOptionalRelay1", DriverTest.testDriveOptionalRelay1), + ("testDriveOptionalBehaviorRelays1", DriverTest.testDriveOptionalBehaviorRelays1), ("testDriveOptionalRelay2", DriverTest.testDriveOptionalRelay2), + ("testDriveOptionalBehaviorRelays2", DriverTest.testDriveOptionalBehaviorRelays2), ("testDriveRelayNoAmbiguity", DriverTest.testDriveRelayNoAmbiguity), - ("testDriveBehaviorRelay", DriverTest.testDriveBehaviorRelay), - ("testDriveBehaviorRelay1", DriverTest.testDriveBehaviorRelay1), - ("testDriveBehaviorRelay2", DriverTest.testDriveBehaviorRelay2), - ("testDriveBehaviorRelay3", DriverTest.testDriveBehaviorRelay3), ] } } @@ -1928,16 +1929,24 @@ final class SignalTests_ : SignalTests, RxTestCase { ("testAsSignal_onErrorDriveWith", SignalTests.testAsSignal_onErrorDriveWith), ("testAsSignal_onErrorRecover", SignalTests.testAsSignal_onErrorRecover), ("testEmitObserver", SignalTests.testEmitObserver), + ("testEmitObservers", SignalTests.testEmitObservers), ("testEmitOptionalObserver", SignalTests.testEmitOptionalObserver), + ("testEmitOptionalObservers", SignalTests.testEmitOptionalObservers), ("testEmitNoAmbiguity", SignalTests.testEmitNoAmbiguity), ("testEmitBehaviorRelay", SignalTests.testEmitBehaviorRelay), + ("testEmitBehaviorRelays", SignalTests.testEmitBehaviorRelays), ("testEmitBehaviorRelay1", SignalTests.testEmitBehaviorRelay1), + ("testEmitBehaviorRelays1", SignalTests.testEmitBehaviorRelays1), ("testEmitBehaviorRelay2", SignalTests.testEmitBehaviorRelay2), - ("testEmitBehaviorRelay3", SignalTests.testEmitBehaviorRelay3), - ("testSignalRelay", SignalTests.testSignalRelay), - ("testSignalOptionalRelay1", SignalTests.testSignalOptionalRelay1), - ("testSignalOptionalRelay2", SignalTests.testSignalOptionalRelay2), - ("testDriveRelayNoAmbiguity", SignalTests.testDriveRelayNoAmbiguity), + ("testEmitBehaviorRelays2", SignalTests.testEmitBehaviorRelays2), + ("testEmitBehaviorRelayNoAmbiguity", SignalTests.testEmitBehaviorRelayNoAmbiguity), + ("testEmitPublishRelay", SignalTests.testEmitPublishRelay), + ("testEmitPublishRelays", SignalTests.testEmitPublishRelays), + ("testEmitOptionalPublishRelay1", SignalTests.testEmitOptionalPublishRelay1), + ("testEmitOptionalPublishRelays", SignalTests.testEmitOptionalPublishRelays), + ("testEmitOptionalPublishRelay2", SignalTests.testEmitOptionalPublishRelay2), + ("testEmitPublishRelays2", SignalTests.testEmitPublishRelays2), + ("testEmitPublishRelayNoAmbiguity", SignalTests.testEmitPublishRelayNoAmbiguity), ] } } diff --git a/Tests/RxCocoaTests/Driver+Test.swift b/Tests/RxCocoaTests/Driver+Test.swift index 5fef1fa09..00177bf8b 100644 --- a/Tests/RxCocoaTests/Driver+Test.swift +++ b/Tests/RxCocoaTests/Driver+Test.swift @@ -317,6 +317,31 @@ extension DriverTest { XCTAssertEqual(events.first?.value.element.flatMap { $0 }, 1) } + + func testDriveObservers() { + var events1: [Recorded>] = [] + var events2: [Recorded>] = [] + + let observer1: AnyObserver = AnyObserver { event in + events1.append(Recorded(time: 0, value: event)) + } + + let observer2: AnyObserver = AnyObserver { event in + events2.append(Recorded(time: 0, value: event)) + } + + _ = (Driver.just(1) as Driver).drive(observer1, observer2) + + XCTAssertEqual(events1, [ + .next(1), + .completed() + ]) + + XCTAssertEqual(events2, [ + .next(1), + .completed() + ]) + } func testDriveOptionalObserver() { var events: [Recorded>] = [] @@ -329,6 +354,31 @@ extension DriverTest { XCTAssertEqual(events.first?.value.element.flatMap { $0 }, 1) } + + func testDriveOptionalObservers() { + var events1: [Recorded>] = [] + var events2: [Recorded>] = [] + + let observer1: AnyObserver = AnyObserver { event in + events1.append(Recorded(time: 0, value: event)) + } + + let observer2: AnyObserver = AnyObserver { event in + events2.append(Recorded(time: 0, value: event)) + } + + _ = (Driver.just(1) as Driver).drive(observer1, observer2) + + XCTAssertEqual(events1, [ + .next(1), + .completed() + ]) + + XCTAssertEqual(events2, [ + .next(1), + .completed() + ]) + } func testDriveNoAmbiguity() { var events: [Recorded>] = [] @@ -344,17 +394,28 @@ extension DriverTest { } } -// MARK: drive relay +// MARK: drive optional behavior relay extension DriverTest { func testDriveRelay() { let relay = BehaviorRelay(value: 0) - - _ = (Driver.just(1) as Driver).drive(relay) - + + let subscription = (Driver.just(1) as Driver).drive(relay) + XCTAssertEqual(relay.value, 1) + subscription.dispose() } - + + func testDriveRelays() { + let relay1 = BehaviorRelay(value: 0) + let relay2 = BehaviorRelay(value: 0) + + _ = Driver.just(1).drive(relay1, relay2) + + XCTAssertEqual(relay1.value, 1) + XCTAssertEqual(relay2.value, 1) + } + func testDriveOptionalRelay1() { let relay = BehaviorRelay(value: 0) @@ -362,6 +423,16 @@ extension DriverTest { XCTAssertEqual(relay.value, 1) } + + func testDriveOptionalBehaviorRelays1() { + let relay1 = BehaviorRelay(value: 0) + let relay2 = BehaviorRelay(value: 0) + + _ = (Driver.just(1) as Driver).drive(relay1, relay2) + + XCTAssertEqual(relay1.value, 1) + XCTAssertEqual(relay2.value, 1) + } func testDriveOptionalRelay2() { let relay = BehaviorRelay(value: 0) @@ -370,6 +441,16 @@ extension DriverTest { XCTAssertEqual(relay.value, 1) } + + func testDriveOptionalBehaviorRelays2() { + let relay1 = BehaviorRelay(value: 0) + let relay2 = BehaviorRelay(value: 0) + + _ = (Driver.just(1) as Driver).drive(relay1, relay2) + + XCTAssertEqual(relay1.value, 1) + XCTAssertEqual(relay2.value, 1) + } func testDriveRelayNoAmbiguity() { let relay = BehaviorRelay(value: 0) @@ -380,44 +461,3 @@ extension DriverTest { XCTAssertEqual(relay.value, 1) } } - -// MARK: drive behavior relay - -extension DriverTest { - func testDriveBehaviorRelay() { - let relay = BehaviorRelay(value: 0) - - let subscription = (Driver.just(1) as Driver).drive(relay) - - XCTAssertEqual(relay.value, 1) - subscription.dispose() - } - - func testDriveBehaviorRelay1() { - let relay = BehaviorRelay(value: 0) - - let subscription = (Driver.just(1) as Driver).drive(relay) - - XCTAssertEqual(relay.value, 1) - subscription.dispose() - } - - func testDriveBehaviorRelay2() { - let relay = BehaviorRelay(value: 0) - - let subscription = (Driver.just(1) as Driver).drive(relay) - - XCTAssertEqual(relay.value, 1) - subscription.dispose() - } - - func testDriveBehaviorRelay3() { - let relay = BehaviorRelay(value: 0) - - // shouldn't cause compile time error - let subscription = Driver.just(1).drive(relay) - - XCTAssertEqual(relay.value, 1) - subscription.dispose() - } -} diff --git a/Tests/RxCocoaTests/Signal+Test.swift b/Tests/RxCocoaTests/Signal+Test.swift index 363b257e6..2b1991c11 100644 --- a/Tests/RxCocoaTests/Signal+Test.swift +++ b/Tests/RxCocoaTests/Signal+Test.swift @@ -257,6 +257,31 @@ extension SignalTests { XCTAssertEqual(events.first?.value.element.flatMap { $0 }, 1) } + + func testEmitObservers() { + var events1: [Recorded>] = [] + var events2: [Recorded>] = [] + + let observer1: AnyObserver = AnyObserver { event in + events1.append(Recorded(time: 0, value: event)) + } + + let observer2: AnyObserver = AnyObserver { event in + events2.append(Recorded(time: 0, value: event)) + } + + _ = (Signal.just(1) as Signal).emit(to: observer1, observer2) + + XCTAssertEqual(events1, [ + .next(1), + .completed() + ]) + + XCTAssertEqual(events2, [ + .next(1), + .completed() + ]) + } func testEmitOptionalObserver() { var events: [Recorded>] = [] @@ -269,6 +294,31 @@ extension SignalTests { XCTAssertEqual(events.first?.value.element.flatMap { $0 }, 1) } + + func testEmitOptionalObservers() { + var events1: [Recorded>] = [] + var events2: [Recorded>] = [] + + let observer1: AnyObserver = AnyObserver { event in + events1.append(Recorded(time: 0, value: event)) + } + + let observer2: AnyObserver = AnyObserver { event in + events2.append(Recorded(time: 0, value: event)) + } + + _ = (Signal.just(1) as Signal).emit(to: observer1, observer2) + + XCTAssertEqual(events1, [ + .next(1), + .completed() + ]) + + XCTAssertEqual(events2, [ + .next(1), + .completed() + ]) + } func testEmitNoAmbiguity() { var events: [Recorded>] = [] @@ -296,6 +346,17 @@ extension SignalTests { subscription.dispose() } + func testEmitBehaviorRelays() { + let relay1 = BehaviorRelay(value: 0) + let relay2 = BehaviorRelay(value: 0) + + let subscription = (Signal.just(1) as Signal).emit(to: relay1, relay2) + + XCTAssertEqual(relay1.value, 1) + XCTAssertEqual(relay2.value, 1) + subscription.dispose() + } + func testEmitBehaviorRelay1() { let relay = BehaviorRelay(value: 0) @@ -305,6 +366,17 @@ extension SignalTests { subscription.dispose() } + func testEmitBehaviorRelays1() { + let relay1 = BehaviorRelay(value: 0) + let relay2 = BehaviorRelay(value: 0) + + let subscription = (Signal.just(1) as Signal).emit(to: relay1, relay2) + + XCTAssertEqual(relay1.value, 1) + XCTAssertEqual(relay2.value, 1) + subscription.dispose() + } + func testEmitBehaviorRelay2() { let relay = BehaviorRelay(value: 0) @@ -314,7 +386,18 @@ extension SignalTests { subscription.dispose() } - func testEmitBehaviorRelay3() { + func testEmitBehaviorRelays2() { + let relay1 = BehaviorRelay(value: 0) + let relay2 = BehaviorRelay(value: 0) + + let subscription = (Signal.just(1) as Signal).emit(to: relay1, relay2) + + XCTAssertEqual(relay1.value, 1) + XCTAssertEqual(relay2.value, 1) + subscription.dispose() + } + + func testEmitBehaviorRelayNoAmbiguity() { let relay = BehaviorRelay(value: 0) // shouldn't cause compile time error @@ -328,7 +411,7 @@ extension SignalTests { // MARK: Emit to relay extension SignalTests { - func testSignalRelay() { + func testEmitPublishRelay() { let relay = PublishRelay() var latest: Int? @@ -340,8 +423,29 @@ extension SignalTests { XCTAssertEqual(latest, 1) } + + func testEmitPublishRelays() { + let relay1 = PublishRelay() + let relay2 = PublishRelay() + + var latest1: Int? + var latest2: Int? + + _ = relay1.subscribe(onNext: { latestElement in + latest1 = latestElement + }) + + _ = relay2.subscribe(onNext: { latestElement in + latest2 = latestElement + }) + + _ = (Signal.just(1) as Signal).emit(to: relay1, relay2) + + XCTAssertEqual(latest1, 1) + XCTAssertEqual(latest2, 1) + } - func testSignalOptionalRelay1() { + func testEmitOptionalPublishRelay1() { let relay = PublishRelay() var latest: Int? = nil @@ -353,8 +457,29 @@ extension SignalTests { XCTAssertEqual(latest, 1) } + + func testEmitOptionalPublishRelays() { + let relay1 = PublishRelay() + let relay2 = PublishRelay() + + var latest1: Int? + var latest2: Int? + + _ = relay1.subscribe(onNext: { latestElement in + latest1 = latestElement + }) + + _ = relay2.subscribe(onNext: { latestElement in + latest2 = latestElement + }) + + _ = (Signal.just(1) as Signal).emit(to: relay1, relay2) + + XCTAssertEqual(latest1, 1) + XCTAssertEqual(latest2, 1) + } - func testSignalOptionalRelay2() { + func testEmitOptionalPublishRelay2() { let relay = PublishRelay() var latest: Int? @@ -366,8 +491,29 @@ extension SignalTests { XCTAssertEqual(latest, 1) } + + func testEmitPublishRelays2() { + let relay1 = PublishRelay() + let relay2 = PublishRelay() + + var latest1: Int? + var latest2: Int? + + _ = relay1.subscribe(onNext: { latestElement in + latest1 = latestElement + }) + + _ = relay2.subscribe(onNext: { latestElement in + latest2 = latestElement + }) + + _ = (Signal.just(1) as Signal).emit(to: relay1, relay2) + + XCTAssertEqual(latest1, 1) + XCTAssertEqual(latest2, 1) + } - func testDriveRelayNoAmbiguity() { + func testEmitPublishRelayNoAmbiguity() { let relay = PublishRelay() var latest: Int? = nil