Guest User

Untitled

a guest
Apr 23rd, 2018
76
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 6.74 KB | None | 0 0
  1. public protocol AssociatedObjectStore {}
  2.  
  3. extension AssociatedObjectStore {
  4. func associatedObject<T>(forKey key: UnsafeRawPointer) -> T? {
  5. return objc_getAssociatedObject(self, key) as? T
  6. }
  7.  
  8. func associatedObject<T>(forKey key: UnsafeRawPointer, default: @autoclosure () -> T) -> T {
  9. if let object: T = self.associatedObject(forKey: key) {
  10. return object
  11. }
  12. let object = `default`()
  13. self.setAssociatedObject(object, forKey: key)
  14. return object
  15. }
  16.  
  17. func setAssociatedObject<T>(_ object: T?, forKey key: UnsafeRawPointer) {
  18. objc_setAssociatedObject(self, key, object, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  19. }
  20. }
  21.  
  22. public struct NoAction {}
  23. public struct NoMutation {}
  24.  
  25. public typealias _Reactor = Reactor
  26.  
  27. public protocol Reactor: class, AssociatedObjectStore {
  28. /// An action represents user actions.
  29. associatedtype Action
  30.  
  31. /// A mutation represents state changes.
  32. associatedtype Mutation = Action
  33.  
  34. /// A State represents the current state of a view.
  35. associatedtype State
  36.  
  37. var action: SafePublishSubject<Action> { get }
  38.  
  39. /// The initial state.
  40. var initialState: State { get }
  41.  
  42. /// The current state. This value is changed just after the state stream emits a new state.
  43. var currentState: State { get }
  44.  
  45. /// The state stream. Use this observable to observe the state changes.
  46. var state: SafeSignal<State> { get }
  47.  
  48. // Transforms the action. Use this function to combine with other observables. This method is
  49. /// called once before the state stream is created.
  50. func transform(action: SafeSignal<Action>) -> SafeSignal<Action>
  51.  
  52. /// Commits mutation from the action. This is the best place to perform side-effects such as
  53. /// async tasks.
  54. func mutate(action: Action) -> SafeSignal<Mutation>
  55.  
  56. /// Transforms the mutation stream. Implement this method to transform or combine with other
  57. /// observables. This method is called once before the state stream is created.
  58. func transform(mutation: SafeSignal<Mutation>) -> SafeSignal<Mutation>
  59.  
  60. /// Generates a new state with the previous state and the action. It should be purely functional
  61. /// so it should not perform any side-effects here. This method is called every time when the
  62. /// mutation is committed.
  63. func reduce(state: State, mutation: Mutation) -> State
  64.  
  65. /// Transforms the state stream. Use this function to perform side-effects such as logging. This
  66. /// method is called once after the state stream is created.
  67. func transform(state: SafeSignal<State>) -> SafeSignal<State>
  68. }
  69.  
  70. public class Stub<Reactor: _Reactor> {
  71. private unowned var reactor: Reactor
  72. private let disposeBag: DisposeBag
  73.  
  74. public var isEnabled: Bool = false
  75.  
  76. public let state: Property<Reactor.State>
  77. public let action: SafePublishSubject<Reactor.Action>
  78. public private(set) var actions: [Reactor.Action] = []
  79.  
  80. public init(reactor: Reactor, disposeBag: DisposeBag) {
  81. self.reactor = reactor
  82. self.disposeBag = disposeBag
  83. self.state = .init(reactor.initialState)
  84. self.state.toSignal()
  85. .observeNext(with: { [weak reactor] state in
  86. reactor?.currentState = state
  87. })
  88. .dispose(in: disposeBag)
  89. self.action = .init()
  90. self.action.toSignal()
  91. .observeNext(with: { [weak self] action in
  92. self?.actions.append(action)
  93. })
  94. .dispose(in: disposeBag)
  95. }
  96. }
  97.  
  98. // MARK: - Associated Object Keys
  99. private var actionKey = "action"
  100. private var currentStateKey = "currentState"
  101. private var stateKey = "state"
  102. private var disposeBagKey = "disposeBag"
  103. private var stubKey = "stub"
  104.  
  105. extension Reactor {
  106. private var _action: SafePublishSubject<Action> {
  107. if self.stub.isEnabled {
  108. return self.stub.action
  109. } else {
  110. return self.associatedObject(forKey: &actionKey, default: .init())
  111. }
  112. }
  113. public var action: SafePublishSubject<Action> {
  114. return self._action
  115. }
  116.  
  117. public internal(set) var currentState: State {
  118. get { return self.associatedObject(forKey: &currentStateKey, default: self.initialState) }
  119. set { self.setAssociatedObject(newValue, forKey: &currentStateKey) }
  120. }
  121.  
  122. private var _state: SafeSignal<State> {
  123. if self.stub.isEnabled {
  124. return self.stub.state.shareReplay()
  125. } else {
  126. return self.associatedObject(forKey: &stateKey, default: self.createStateStream())
  127. }
  128. }
  129.  
  130. public var state: SafeSignal<State> {
  131. // It seems that Swift has a bug in associated object when subclassing a generic class. This is
  132. // a temporary solution to bypass the bug. See #30 for details.
  133. return self.state.shareReplay()
  134. }
  135.  
  136. fileprivate var disposeBag: DisposeBag {
  137. get { return self.associatedObject(forKey: &disposeBagKey, default: DisposeBag()) }
  138. }
  139.  
  140. public func createStateStream() -> SafeSignal<State> {
  141. let action = self._action.toSignal()
  142. let transformedAction = self.transform(action: action)
  143. let mutation = transformedAction.flatMapLatest { [weak self] action -> SafeSignal<Mutation> in
  144. guard let `self` = self else { return SafeSignal.completed() }
  145. return self.mutate(action: action)
  146. }
  147. let transformedMutation = self.transform(mutation: mutation)
  148. let state = transformedMutation
  149. .scan(self.initialState) { [weak self] state, mutation -> State in
  150. guard let `self` = self else { return state }
  151. return self.reduce(state: state, mutation: mutation)
  152. }
  153. .start(with: self.initialState)
  154. .observeOn(.main)
  155.  
  156. let transformedState = self.transform(state: state)
  157. .doOn(next: { [weak self] state in
  158. self?.currentState = state
  159. })
  160. return transformedState
  161. }
  162.  
  163. public func transform(action: SafeSignal<Action>) -> SafeSignal<Action> {
  164. return action
  165. }
  166. public func mutate(action: Action) -> SafeSignal<Mutation> {
  167. return SafeSignal<Mutation>.completed()
  168. }
  169. public func transform(mutation: SafeSignal<Mutation>) -> SafeSignal<Mutation> {
  170. return mutation
  171. }
  172. public func reduce(state: State, mutation: Mutation) -> State {
  173. return state
  174. }
  175. public func transform(state: SafeSignal<State>) -> SafeSignal<State> {
  176. return state
  177. }
  178. }
  179.  
  180. extension Reactor where Action == Mutation {
  181. public func mutate(action: Action) -> SafeSignal<Mutation> {
  182. return .just(action)
  183. }
  184. }
  185.  
  186. extension Reactor {
  187. public var stub: Stub<Self> {
  188. return self.associatedObject(
  189. forKey: &stubKey,
  190. default: .init(reactor: self, disposeBag: self.disposeBag)
  191. )
  192. }
  193. }
Add Comment
Please, Sign In to add comment