From 938e55a87331f84fe45776f5e7551fd4a682826e Mon Sep 17 00:00:00 2001 From: mosamer Date: Tue, 21 Nov 2017 22:28:01 +0100 Subject: [PATCH 1/5] remove `_enabledIf` & `workFactory` from public API --- Sources/Action/Action.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Sources/Action/Action.swift b/Sources/Action/Action.swift index 8ed51961..d8f873c1 100644 --- a/Sources/Action/Action.swift +++ b/Sources/Action/Action.swift @@ -22,9 +22,6 @@ When this excuted via execute() or inputs subject, it passes its parameter to th public final class Action { public typealias WorkFactory = (Input) -> Observable - public let _enabledIf: Observable - public let workFactory: WorkFactory - /// Inputs that triggers execution of action. /// This subject also includes inputs as aguments of execute(). /// All inputs are always appear in this subject even if the action is not enabled. @@ -67,9 +64,6 @@ public final class Action { enabledIf: Observable = Observable.just(true), workFactory: @escaping WorkFactory) { - self._enabledIf = enabledIf - self.workFactory = workFactory - let enabledSubject = BehaviorSubject(value: false) enabled = enabledSubject.asObservable() From 9e82d3a60c99202ad90eb2801b6a1bf10592a9f0 Mon Sep 17 00:00:00 2001 From: mosamer Date: Tue, 21 Nov 2017 22:43:54 +0100 Subject: [PATCH 2/5] rename `executing` ~> `isExecuting` --- Demo/ViewController.swift | 6 +++--- Sources/Action/Action.swift | 14 +++++++++++--- Tests/ActionTests/ActionTests.swift | 17 ++++++++--------- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/Demo/ViewController.swift b/Demo/ViewController.swift index 415f907a..572274b4 100644 --- a/Demo/ViewController.swift +++ b/Demo/ViewController.swift @@ -67,8 +67,8 @@ class ViewController: UIViewController { // Demo: observe the output of both actions, spin an activity indicator // while performing the work Observable.combineLatest( - button.rx.action!.executing, - self.navigationItem.rightBarButtonItem!.rx.action!.executing) { a,b in + button.rx.action!.isExecuting, + self.navigationItem.rightBarButtonItem!.rx.action!.isExecuting) { a,b in // we combine two boolean observable and output one boolean return a || b } @@ -92,7 +92,7 @@ class ViewController: UIViewController { } self.navigationItem.leftBarButtonItem?.rx.bind(to: sharedAction, input: .barButton) - sharedAction.executing.debounce(0, scheduler: MainScheduler.instance).subscribe(onNext: { [weak self] executing in + sharedAction.isExecuting.debounce(0, scheduler: MainScheduler.instance).subscribe(onNext: { [weak self] executing in if (executing) { self?.activityIndicator.startAnimating() } diff --git a/Sources/Action/Action.swift b/Sources/Action/Action.swift index d8f873c1..ee60d07e 100644 --- a/Sources/Action/Action.swift +++ b/Sources/Action/Action.swift @@ -37,7 +37,7 @@ public final class Action { public let elements: Observable /// Whether or not we're currently executing. - public let executing: Observable + public let isExecuting: Observable /// Observables returned by the workFactory. /// Useful for sending results back from work being completed @@ -87,7 +87,7 @@ public final class Action { elements = executionObservables .flatMap { $0.catchError { _ in Observable.empty() } } - executing = executionObservables.flatMap { + isExecuting = executionObservables.flatMap { execution -> Observable in let execution = execution .flatMap { _ in Observable.empty() } @@ -101,7 +101,7 @@ public final class Action { .share(replay: 1, scope: .forever) Observable - .combineLatest(executing, enabledIf) { !$0 && $1 } + .combineLatest(isExecuting, enabledIf) { !$0 && $1 } .bind(to: enabledSubject) .disposed(by: disposeBag) } @@ -129,3 +129,11 @@ public final class Action { return subject.asObservable() } } + +// MARK: Deprecated +extension Action { + @available(*, deprecated, renamed: "isExecuting") + public var executing: Observable { + return isExecuting + } +} diff --git a/Tests/ActionTests/ActionTests.swift b/Tests/ActionTests/ActionTests.swift index 5d8ce029..5486e0cb 100644 --- a/Tests/ActionTests/ActionTests.swift +++ b/Tests/ActionTests/ActionTests.swift @@ -79,7 +79,7 @@ class ActionTests: QuickSpec { .bind(to: enabled) .disposed(by: disposeBag) - action.executing + action.isExecuting .bind(to: executing) .disposed(by: disposeBag) @@ -91,14 +91,13 @@ class ActionTests: QuickSpec { .bind(to: underlyingError) .disposed(by: disposeBag) - // Dummy subscription for multiple subcription tests - action.inputs.subscribe().disposed(by: disposeBag) - action.elements.subscribe().disposed(by: disposeBag) - action.errors.subscribe().disposed(by: disposeBag) - action.enabled.subscribe().disposed(by: disposeBag) - action.executing.subscribe().disposed(by: disposeBag) - action.executionObservables.subscribe().disposed(by: disposeBag) - action.underlyingError.subscribe().disposed(by: disposeBag) + // Dummy subscription for multiple subcription tests + action.inputs.subscribe().disposed(by: disposeBag) + action.elements.subscribe().disposed(by: disposeBag) + action.errors.subscribe().disposed(by: disposeBag) + action.enabled.subscribe().disposed(by: disposeBag) + action.isExecuting.subscribe().disposed(by: disposeBag) + action.executionObservables.subscribe().disposed(by: disposeBag) } describe("single element action") { From ec84fadc61f62a8d463da230dc5958722aeb1411 Mon Sep 17 00:00:00 2001 From: mosamer Date: Tue, 21 Nov 2017 22:46:35 +0100 Subject: [PATCH 3/5] rename `enabled` ~> `isEnabled` --- Sources/Action/Action.swift | 11 ++++++++--- Sources/Action/CommonUI/Button+Action.swift | 2 +- Sources/Action/CommonUI/Control+Action.swift | 2 +- .../Action/UIKitExtensions/UIAlertAction+Action.swift | 6 +++--- .../UIKitExtensions/UIBarButtonItem+Action.swift | 4 ++-- Tests/ActionTests/ActionTests.swift | 4 ++-- 6 files changed, 17 insertions(+), 12 deletions(-) diff --git a/Sources/Action/Action.swift b/Sources/Action/Action.swift index ee60d07e..d966ca88 100644 --- a/Sources/Action/Action.swift +++ b/Sources/Action/Action.swift @@ -47,7 +47,7 @@ public final class Action { /// Whether or not we're enabled. Note that this is a *computed* sequence /// property based on enabledIf initializer and if we're currently executing. /// Always observed on MainScheduler. - public let enabled: Observable + public let isEnabled: Observable private let disposeBag = DisposeBag() @@ -65,13 +65,13 @@ public final class Action { workFactory: @escaping WorkFactory) { let enabledSubject = BehaviorSubject(value: false) - enabled = enabledSubject.asObservable() + isEnabled = enabledSubject.asObservable() let errorsSubject = PublishSubject() errors = errorsSubject.asObservable() executionObservables = inputs - .withLatestFrom(enabled) { input, enabled in (input, enabled) } + .withLatestFrom(isEnabled) { input, enabled in (input, enabled) } .flatMap { input, enabled -> Observable> in if enabled { return Observable.of(workFactory(input) @@ -136,4 +136,9 @@ extension Action { public var executing: Observable { return isExecuting } + + @available(*, deprecated, renamed: "isEnabled") + public var enabled: Observable { + return isEnabled + } } diff --git a/Sources/Action/CommonUI/Button+Action.swift b/Sources/Action/CommonUI/Button+Action.swift index 2298dd17..a4560ba4 100644 --- a/Sources/Action/CommonUI/Button+Action.swift +++ b/Sources/Action/CommonUI/Button+Action.swift @@ -31,7 +31,7 @@ public extension Reactive where Base: Button { // Set up new bindings, if applicable. if let action = newValue { action - .enabled + .isEnabled .bind(to: self.isEnabled) .disposed(by: self.base.actionDisposeBag) diff --git a/Sources/Action/CommonUI/Control+Action.swift b/Sources/Action/CommonUI/Control+Action.swift index 484e71d2..c84bd5b4 100644 --- a/Sources/Action/CommonUI/Control+Action.swift +++ b/Sources/Action/CommonUI/Control+Action.swift @@ -26,7 +26,7 @@ public extension Reactive where Base: Control { // Bind the enabled state of the control to the enabled state of the action action - .enabled + .isEnabled .bind(to: self.isEnabled) .disposed(by: self.base.actionDisposeBag) } diff --git a/Sources/Action/UIKitExtensions/UIAlertAction+Action.swift b/Sources/Action/UIKitExtensions/UIAlertAction+Action.swift index 70164740..3c07029e 100644 --- a/Sources/Action/UIKitExtensions/UIAlertAction+Action.swift +++ b/Sources/Action/UIKitExtensions/UIAlertAction+Action.swift @@ -41,14 +41,14 @@ public extension Reactive where Base: UIAlertAction { // Set up new bindings, if applicable. if let action = newValue { action - .enabled - .bind(to: self.enabled) + .isEnabled + .bind(to: self.isEnabled) .disposed(by: self.base.actionDisposeBag) } } } - var enabled: AnyObserver { + public var isEnabled: AnyObserver { return AnyObserver { [weak base] event in MainScheduler.ensureExecutingOnScheduler() diff --git a/Sources/Action/UIKitExtensions/UIBarButtonItem+Action.swift b/Sources/Action/UIKitExtensions/UIBarButtonItem+Action.swift index d2d9a32f..5c36b42d 100644 --- a/Sources/Action/UIKitExtensions/UIBarButtonItem+Action.swift +++ b/Sources/Action/UIKitExtensions/UIBarButtonItem+Action.swift @@ -26,7 +26,7 @@ public extension Reactive where Base: UIBarButtonItem { // Set up new bindings, if applicable. if let action = newValue { action - .enabled + .isEnabled .bind(to: self.isEnabled) .disposed(by: self.base.actionDisposeBag) @@ -47,7 +47,7 @@ public extension Reactive where Base: UIBarButtonItem { .disposed(by: self.base.actionDisposeBag) action - .enabled + .isEnabled .bind(to: self.isEnabled) .disposed(by: self.base.actionDisposeBag) } diff --git a/Tests/ActionTests/ActionTests.swift b/Tests/ActionTests/ActionTests.swift index 5486e0cb..ac251dfd 100644 --- a/Tests/ActionTests/ActionTests.swift +++ b/Tests/ActionTests/ActionTests.swift @@ -75,7 +75,7 @@ class ActionTests: QuickSpec { .bind(to: errors) .disposed(by: disposeBag) - action.enabled + action.isEnabled .bind(to: enabled) .disposed(by: disposeBag) @@ -95,7 +95,7 @@ class ActionTests: QuickSpec { action.inputs.subscribe().disposed(by: disposeBag) action.elements.subscribe().disposed(by: disposeBag) action.errors.subscribe().disposed(by: disposeBag) - action.enabled.subscribe().disposed(by: disposeBag) + action.isEnabled.subscribe().disposed(by: disposeBag) action.isExecuting.subscribe().disposed(by: disposeBag) action.executionObservables.subscribe().disposed(by: disposeBag) } From 675e1caa408cd8999e344f165019b0b3f11a6868 Mon Sep 17 00:00:00 2001 From: mosamer Date: Wed, 22 Nov 2017 09:09:35 +0100 Subject: [PATCH 4/5] update readme --- Readme.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Readme.md b/Readme.md index d09c6f1e..6cc536c1 100644 --- a/Readme.md +++ b/Readme.md @@ -20,7 +20,7 @@ Usage You have to pass a `workFactory` that takes input and returns an `Observable`. This represents some work that needs to be accomplished. Whenever you call `execute()`, you pass in input that's fed to the work factory. The `Action` will subscribe to the observable and emit the `Next` events on its `elements` property. If the observable errors, the error is sent as a `Next` even on the `errors` property. Neat. -Actions can only execute one thing at a time. If you try to execute an action that's currently executing, you'll get an error. The `executing` property sends `true` and `false` values as `Next` events. +Actions can only execute one thing at a time. If you try to execute an action that's currently executing, you'll get an error. The `isExecuting` property sends `true` and `false` values as `Next` events. ```swift action: Action = Action(workFactory: { input in @@ -46,7 +46,7 @@ action: Action = Action(enabledIf: validEmailAddress, workFactory: Now `execute()` only does the work if the email address is valid. Super cool! -Note that `enabledIf` isn't the same as the `enabled` property. You pass in `enabledIf` and the action uses that, and its current executing state, to determine if it's currently enabled. +Note that `enabledIf` isn't the same as the `isEnabled` property. You pass in `enabledIf` and the action uses that, and its current executing state, to determine if it's currently enabled. What's _really_ cool is the `UIButton` extension. It accepts a `CocoaAction`, which is just `Action`. @@ -54,11 +54,11 @@ What's _really_ cool is the `UIButton` extension. It accepts a `CocoaAction`, wh button.rx.action = action ``` -Now when the button is pressed, the action is executed. The button's `enabled` state is bound to the action's `enabled` property. That means you can feed your form-validation logic into the action as a signal, and your button's enabled state is handled for you. Also, the user can't press the button again before the action is done executing, since it only handles one thing at a time. Cool. Check out [this code example of CocoaAction _in_ action](https://github.com/artsy/eidolon/blob/cb31168fa29dcc7815fd4a2e30e7c000bd1820ce/Kiosk/Bid%20Fulfillment/GenericFormValidationViewModel.swift). +Now when the button is pressed, the action is executed. The button's `isEnabled` state is bound to the action's `isEnabled` property. That means you can feed your form-validation logic into the action as a signal, and your button's enabled state is handled for you. Also, the user can't press the button again before the action is done executing, since it only handles one thing at a time. Cool. Check out [this code example of CocoaAction _in_ action](https://github.com/artsy/eidolon/blob/cb31168fa29dcc7815fd4a2e30e7c000bd1820ce/Kiosk/Bid%20Fulfillment/GenericFormValidationViewModel.swift). If you'd like to use `Action` to do a complex operation such as file download with download progress report (to update progress bar in the UI for example) you'd use `Action` instead of `CocoaAction`. Out of the box `CocoaAction` can't emit progress values, your own `Action` will do that. For details refer to [this article](http://www.sm-cloud.com/rxswift-action/). -If your scenario involves many buttons that needs to trigger the same `Action` providing different input, you can use `bindTo` on each `UIButton` with a closure that returns correct input. +If your scenario involves many buttons that needs to trigger the same `Action` providing different input, you can use `bind(to:)` on each `UIButton` with a closure that returns correct input. ```swift let button1 = UIButton() @@ -68,13 +68,13 @@ let action = Action { input in print(input) return .just(input) } -button1.rx.bindTo(action) { _ in return "Hello"} -button2.rx.bindTo(action) { _ in return "Goodbye"} +button1.rx.bind(to: action) {_ in return "Hello"} +button2.rx.bind(to: action) {_ in return "Goodbye"} ``` `button1` and `button2` are sharing the same `Action`, but they are feeding it with different input (`Hello` and `Goodbye` that will be printed for corresponding tap). -A more complex use case can be a single action related to a `UIViewController` that manages your navigation, error handling and loading state. With this approach, you can have as many `UIButton`s (or `UIBarButtonItem`s) as you want and subscribe to `executing`, `errors` and `elements` once and in a single common place. +A more complex use case can be a single action related to a `UIViewController` that manages your navigation, error handling and loading state. With this approach, you can have as many `UIButton`s (or `UIBarButtonItem`s) as you want and subscribe to `isExecuting`, `errors` and `elements` once and in a single common place. There's also a really cool extension on `UIAlertAction`, used by [`UIAlertController`](http://ashfurrow.com/blog/uialertviewcontroller-example/). One catch: because of the limitations of that class, you can't instantiate it with the normal initializer. Instead, call this class method: From ba20f14e3cfe605ecf123b0b4d3dfec2ff13c1bd Mon Sep 17 00:00:00 2001 From: mosamer Date: Wed, 22 Nov 2017 09:17:50 +0100 Subject: [PATCH 5/5] update changelog --- Changelog.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Changelog.md b/Changelog.md index c64903af..efa931e8 100644 --- a/Changelog.md +++ b/Changelog.md @@ -50,6 +50,18 @@ Current master - Added full support for RxSwift 4.2.0 - UIRefreshControl support: binding to an action (or CocoaAction) starts the action itself and updates the control's refreshing status +4.0 (In development) +-------------------- + +- Cleanup public API [#120](https://github.com/RxSwiftCommunity/Action/issues/120) + - Remove unneccessary properties + - `workFactory` + - `_enabledIf` + - Rename properties to match Swift API design [guidelines](https://swift.org/documentation/api-design-guidelines/#strive-for-fluent-usage) + - `enabled` ~> `isEnabled` + - `executing` ~> `isExecuting` + - Deprecate renamed properties + 3.5.0 -----