Permalink
Browse files

Merge pull request #82 from tokijh/master

Use StateRelay instead of Variable
  • Loading branch information...
devxoul committed Oct 23, 2018
2 parents 1322f24 + 3e2717a commit 69072cec40f56978d29a1df84ed1ef1f155513dd
Showing with 185 additions and 6 deletions.
  1. +4 −4 README.md
  2. +52 −0 Sources/ReactorKit/StateRelay.swift
  3. +2 −2 Sources/ReactorKit/Stub.swift
  4. +127 −0 Tests/ReactorKitTests/StateRelayTests.swift
View
@@ -211,13 +211,13 @@ func transform(action: Observable<Action>) -> Observable<Action> {
### Global States
Unlike Redux, ReactorKit doesn't define a global app state. It means that you can use anything to manage a global state. You can use a `Variable`, a `PublishSubject` or even a reactor. ReactorKit doesn't force to have a global state so you can use ReactorKit in a specific feature in your application.
Unlike Redux, ReactorKit doesn't define a global app state. It means that you can use anything to manage a global state. You can use a `BehaviorSubject`, a `PublishSubject` or even a reactor. ReactorKit doesn't force to have a global state so you can use ReactorKit in a specific feature in your application.
There is no global state in the **Action → Mutation → State** flow. You should use `transform(mutation:)` to transform the global state to a mutation. Let's assume that we have a global `Variable` which stores the current authenticated user. If you'd like to emit a `Mutation.setUser(User?)` when the `currentUser` is changed, you can do as following:
There is no global state in the **Action → Mutation → State** flow. You should use `transform(mutation:)` to transform the global state to a mutation. Let's assume that we have a global `BehaviorSubject` which stores the current authenticated user. If you'd like to emit a `Mutation.setUser(User?)` when the `currentUser` is changed, you can do as following:
```swift
var currentUser: Variable<User> // global state
var currentUser: BehaviorSubject<User> // global state
func transform(mutation: Observable<Mutation>) -> Observable<Mutation> {
return Observable.merge(mutation, currentUser.map(Mutation.setUser))
@@ -273,7 +273,7 @@ A view can be tested with a *stub* reactor. A reactor has a property `stub` whic
```swift
var isEnabled: Bool { get set }
var state: Variable<Reactor.State> { get }
var state: StateRelay<Reactor.State> { get }
var action: ActionSubject<Reactor.Action> { get }
var actions: [Reactor.Action] { get } // recorded actions
```
@@ -0,0 +1,52 @@
//
// StateRelay.swift
// ReactorKit
//
// Created by tokijh on 05/10/2018.
//
import RxSwift
/// StateRelay is a wrapper for `BehaviorSubject`.
///
/// Unlike `BehaviorSubject` it can't terminate with error or completed.
public final class StateRelay<Element>: ObservableType {
public typealias E = Element
private let _subject: BehaviorSubject<Element>
/// Accepts `event` and emits it to subscribers
public func accept(_ event: Element) {
_subject.onNext(event)
}
/// Gets or sets current value of behavior subject
///
/// Whenever a new value is set, all the observers are notified of the change.
///
/// Even if the newly set value is same as the old value, observers are still notified for change.
public var value: Element {
get {
// this try! is ok because subject can't error out or be disposed
return try! _subject.value()
}
set(newValue) {
accept(newValue)
}
}
/// Initializes behavior relay with initial value.
public init(value: Element) {
_subject = BehaviorSubject(value: value)
}
/// Subscribes observer
public func subscribe<O: ObserverType>(_ observer: O) -> Disposable where O.E == E {
return _subject.subscribe(observer)
}
/// - returns: Canonical interface for push style sequence
public func asObservable() -> Observable<Element> {
return _subject.asObservable()
}
}
@@ -6,14 +6,14 @@ public class Stub<Reactor: ReactorKit.Reactor> {
public var isEnabled: Bool = false
public let state: Variable<Reactor.State>
public let state: StateRelay<Reactor.State>
public let action: ActionSubject<Reactor.Action>
public private(set) var actions: [Reactor.Action] = []
public init(reactor: Reactor, disposeBag: DisposeBag) {
self.reactor = reactor
self.disposeBag = disposeBag
self.state = .init(reactor.initialState)
self.state = .init(value: reactor.initialState)
self.state.asObservable()
.subscribe(onNext: { [weak reactor] state in
reactor?.currentState = state
@@ -0,0 +1,127 @@
//
// StateRelayTests.swift
// ReactorKit
//
// Created by tokijh on 21/10/2018.
//
import XCTest
import RxExpect
import RxSwift
import RxTest
@testable import ReactorKit
class StateRelayTests: XCTestCase {
func testInitialValues() {
let a = StateRelay(value: 1)
let b = StateRelay(value: 2)
let c = Observable.combineLatest(a.asObservable(), b.asObservable(), resultSelector: +)
var latestValue: Int?
let subscription = c
.subscribe(onNext: { next in
latestValue = next
})
XCTAssertEqual(latestValue!, 3)
a.value = 5
XCTAssertEqual(latestValue!, 7)
b.value = 9
XCTAssertEqual(latestValue!, 14)
subscription.dispose()
a.value = 10
XCTAssertEqual(latestValue!, 14)
}
func testAccept() {
let relay = StateRelay(value: 0)
relay.accept(100)
XCTAssertEqual(relay.value, 100)
relay.accept(200)
XCTAssertEqual(relay.value, 200)
relay.accept(300)
XCTAssertEqual(relay.value, 300)
}
func testDoNotSendsCompletedOnDealloc() {
var a = StateRelay(value: 1)
var latest = 0
var completed = false
let disposable = a.asObservable().debug().subscribe(onNext: { n in
latest = n
}, onCompleted: {
completed = true
})
XCTAssertEqual(latest, 1)
XCTAssertFalse(completed)
a = StateRelay(value: 2)
XCTAssertEqual(latest, 1)
XCTAssertFalse(completed)
disposable.dispose()
XCTAssertEqual(latest, 1)
XCTAssertFalse(completed)
}
func testVariableREADMEExampleByStateRelay() {
// Two simple Rx variables
// Every variable is actually a sequence future values in disguise.
let a /*: Observable<Int>*/ = StateRelay(value: 1)
let b /*: Observable<Int>*/ = StateRelay(value: 2)
// Computed third variable (or sequence)
let c /*: Observable<Int>*/ = Observable.combineLatest(a.asObservable(), b.asObservable()) { $0 + $1 }
// Reading elements from c.
// This is just a demo example.
// Sequence elements are usually never enumerated like this.
// Sequences are usually combined using map/filter/combineLatest ...
//
// This will immediately print:
// Next value of c = 3
// because variables have initial values (starting element)
var latestValueOfC : Int? = nil
// let _ = doesn't retain.
let d/*: Disposable*/ = c
.subscribe(onNext: { c in
//print("Next value of c = \(c)")
latestValueOfC = c
})
defer {
d.dispose()
}
XCTAssertEqual(latestValueOfC!, 3)
// This will print:
// Next value of c = 5
a.value = 3
XCTAssertEqual(latestValueOfC!, 5)
// This will print:
// Next value of c = 8
b.value = 5
XCTAssertEqual(latestValueOfC!, 8)
}
}

0 comments on commit 69072ce

Please sign in to comment.