Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- public protocol AssociatedObjectStore {}
- extension AssociatedObjectStore {
- func associatedObject<T>(forKey key: UnsafeRawPointer) -> T? {
- return objc_getAssociatedObject(self, key) as? T
- }
- func associatedObject<T>(forKey key: UnsafeRawPointer, default: @autoclosure () -> T) -> T {
- if let object: T = self.associatedObject(forKey: key) {
- return object
- }
- let object = `default`()
- self.setAssociatedObject(object, forKey: key)
- return object
- }
- func setAssociatedObject<T>(_ object: T?, forKey key: UnsafeRawPointer) {
- objc_setAssociatedObject(self, key, object, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
- }
- }
- public struct NoAction {}
- public struct NoMutation {}
- public typealias _Reactor = Reactor
- public protocol Reactor: class, AssociatedObjectStore {
- /// An action represents user actions.
- associatedtype Action
- /// A mutation represents state changes.
- associatedtype Mutation = Action
- /// A State represents the current state of a view.
- associatedtype State
- var action: SafePublishSubject<Action> { get }
- /// The initial state.
- var initialState: State { get }
- /// The current state. This value is changed just after the state stream emits a new state.
- var currentState: State { get }
- /// The state stream. Use this observable to observe the state changes.
- var state: SafeSignal<State> { get }
- // Transforms the action. Use this function to combine with other observables. This method is
- /// called once before the state stream is created.
- func transform(action: SafeSignal<Action>) -> SafeSignal<Action>
- /// Commits mutation from the action. This is the best place to perform side-effects such as
- /// async tasks.
- func mutate(action: Action) -> SafeSignal<Mutation>
- /// Transforms the mutation stream. Implement this method to transform or combine with other
- /// observables. This method is called once before the state stream is created.
- func transform(mutation: SafeSignal<Mutation>) -> SafeSignal<Mutation>
- /// Generates a new state with the previous state and the action. It should be purely functional
- /// so it should not perform any side-effects here. This method is called every time when the
- /// mutation is committed.
- func reduce(state: State, mutation: Mutation) -> State
- /// Transforms the state stream. Use this function to perform side-effects such as logging. This
- /// method is called once after the state stream is created.
- func transform(state: SafeSignal<State>) -> SafeSignal<State>
- }
- public class Stub<Reactor: _Reactor> {
- private unowned var reactor: Reactor
- private let disposeBag: DisposeBag
- public var isEnabled: Bool = false
- public let state: Property<Reactor.State>
- public let action: SafePublishSubject<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.toSignal()
- .observeNext(with: { [weak reactor] state in
- reactor?.currentState = state
- })
- .dispose(in: disposeBag)
- self.action = .init()
- self.action.toSignal()
- .observeNext(with: { [weak self] action in
- self?.actions.append(action)
- })
- .dispose(in: disposeBag)
- }
- }
- // MARK: - Associated Object Keys
- private var actionKey = "action"
- private var currentStateKey = "currentState"
- private var stateKey = "state"
- private var disposeBagKey = "disposeBag"
- private var stubKey = "stub"
- extension Reactor {
- private var _action: SafePublishSubject<Action> {
- if self.stub.isEnabled {
- return self.stub.action
- } else {
- return self.associatedObject(forKey: &actionKey, default: .init())
- }
- }
- public var action: SafePublishSubject<Action> {
- return self._action
- }
- public internal(set) var currentState: State {
- get { return self.associatedObject(forKey: ¤tStateKey, default: self.initialState) }
- set { self.setAssociatedObject(newValue, forKey: ¤tStateKey) }
- }
- private var _state: SafeSignal<State> {
- if self.stub.isEnabled {
- return self.stub.state.shareReplay()
- } else {
- return self.associatedObject(forKey: &stateKey, default: self.createStateStream())
- }
- }
- public var state: SafeSignal<State> {
- // It seems that Swift has a bug in associated object when subclassing a generic class. This is
- // a temporary solution to bypass the bug. See #30 for details.
- return self.state.shareReplay()
- }
- fileprivate var disposeBag: DisposeBag {
- get { return self.associatedObject(forKey: &disposeBagKey, default: DisposeBag()) }
- }
- public func createStateStream() -> SafeSignal<State> {
- let action = self._action.toSignal()
- let transformedAction = self.transform(action: action)
- let mutation = transformedAction.flatMapLatest { [weak self] action -> SafeSignal<Mutation> in
- guard let `self` = self else { return SafeSignal.completed() }
- return self.mutate(action: action)
- }
- let transformedMutation = self.transform(mutation: mutation)
- let state = transformedMutation
- .scan(self.initialState) { [weak self] state, mutation -> State in
- guard let `self` = self else { return state }
- return self.reduce(state: state, mutation: mutation)
- }
- .start(with: self.initialState)
- .observeOn(.main)
- let transformedState = self.transform(state: state)
- .doOn(next: { [weak self] state in
- self?.currentState = state
- })
- return transformedState
- }
- public func transform(action: SafeSignal<Action>) -> SafeSignal<Action> {
- return action
- }
- public func mutate(action: Action) -> SafeSignal<Mutation> {
- return SafeSignal<Mutation>.completed()
- }
- public func transform(mutation: SafeSignal<Mutation>) -> SafeSignal<Mutation> {
- return mutation
- }
- public func reduce(state: State, mutation: Mutation) -> State {
- return state
- }
- public func transform(state: SafeSignal<State>) -> SafeSignal<State> {
- return state
- }
- }
- extension Reactor where Action == Mutation {
- public func mutate(action: Action) -> SafeSignal<Mutation> {
- return .just(action)
- }
- }
- extension Reactor {
- public var stub: Stub<Self> {
- return self.associatedObject(
- forKey: &stubKey,
- default: .init(reactor: self, disposeBag: self.disposeBag)
- )
- }
- }
Add Comment
Please, Sign In to add comment