-
-
Save shakemno/8e5d2ee83932f4ba74c377360428c085 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import XCPlayground | |
/*: | |
# Reflux | |
*/ | |
//: Interfaces | |
protocol State { | |
init() | |
var userState: UserState {get} | |
} | |
protocol MutableState: State { | |
var userState: UserState {get set} | |
} | |
protocol StateSubscriber: class { | |
func applyState(state: State) | |
} | |
protocol Action { | |
func execute(state: MutableState) -> MutableState | |
} | |
protocol ActionCreating { | |
func createAction(state: State, completion: (Action?) -> ()) | |
} | |
protocol ActionCreatingAction: ActionCreating, Action {} | |
protocol Store { | |
var state: State {get} | |
func addStateSubscriber(subscriber: StateSubscriber) | |
func removeStateSubscriber(subscriber: StateSubscriber) | |
} | |
protocol Dispatcher { | |
func dispatch(action: Action) | |
func dispatch(actionCreating: ActionCreating) | |
func dispatch(actionCreatingAction: ActionCreatingAction) | |
} | |
//: Store | |
final class AppStore: Store { | |
static let sharedInstance = AppStore(initialState: AppState()) | |
private let dispatchQueue = dispatch_queue_create("redux-store-dispatch-q", DISPATCH_QUEUE_SERIAL) | |
private var _state: MutableState | |
var state: State { | |
var state: State! | |
dispatch_sync(dispatchQueue) { | |
state = self._state | |
} | |
return state | |
} | |
private var subscribers: [StateSubscriber] = [] | |
init(initialState: MutableState) { | |
_state = initialState | |
} | |
func addStateSubscriber(subscriber: StateSubscriber) { | |
dispatch_async(dispatchQueue) { | |
self.subscribers.append(subscriber) | |
} | |
} | |
func removeStateSubscriber(subscriber: StateSubscriber) { | |
if let index = subscribers.indexOf({ return $0 === subscriber }) { | |
dispatch_async(dispatchQueue) { | |
self.subscribers.removeAtIndex(index) | |
} | |
} | |
} | |
} | |
//: Dispatcher | |
extension AppStore: Dispatcher { | |
func dispatch(action: Action) { | |
dispatch_async(dispatchQueue) { | |
let newState = action.execute(self._state) | |
self._state = newState | |
for subsciber in self.subscribers { | |
subsciber.applyState(self._state) | |
} | |
} | |
} | |
func dispatch(actionCreating: ActionCreating) { | |
actionCreating.createAction(state) { [weak self] action in | |
guard let action = action | |
else { return } | |
self?.dispatch(action) | |
} | |
} | |
func dispatch(actionCreatingAction: ActionCreatingAction) { | |
dispatch(actionCreatingAction as ActionCreating) | |
dispatch(actionCreatingAction as Action) | |
} | |
} | |
//: State | |
struct AppState: MutableState { | |
var userState = UserState() | |
} | |
//: Helpers | |
protocol StoreConsumer { | |
var store: AppStore {get} | |
} | |
extension StoreConsumer { | |
var store: AppStore { | |
return AppStore.sharedInstance | |
} | |
} | |
//: Store Tests | |
func test_store_dispatch_queue() { | |
struct BlockingAction: Action { | |
let semaphore = dispatch_semaphore_create(0) | |
func execute(state: MutableState) -> MutableState { | |
var mutableState = state | |
mutableState.userState.user?.firstName = "Bobby" | |
let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(5 * Double(NSEC_PER_SEC))) | |
dispatch_semaphore_wait(semaphore, delayTime) | |
return mutableState | |
} | |
} | |
struct NonBlockingAction: Action { | |
func execute(state: MutableState) -> MutableState { | |
var mutableState = state | |
mutableState.userState.user?.firstName = "Barry" | |
return mutableState | |
} | |
} | |
var state = AppState() | |
var user = User() | |
user.firstName = "Bobby" | |
state.userState.user = user | |
let store = AppStore(initialState: state) | |
let firstAction = BlockingAction() | |
let secondAction = NonBlockingAction() | |
let concurrentQueue = dispatch_queue_create("testQ", DISPATCH_QUEUE_CONCURRENT) | |
dispatch_async(concurrentQueue) { | |
store.dispatch(firstAction) | |
} | |
dispatch_async(concurrentQueue) { | |
store.dispatch(secondAction) | |
} | |
// wait 6 seconds to ensure both actions have executed, irl this would be implemented better | |
let semaphore = dispatch_semaphore_create(0) | |
let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(6 * Double(NSEC_PER_SEC))) | |
dispatch_semaphore_wait(semaphore, delayTime) | |
assert(store.state.userState.user?.firstName == "Barry" , "Actual Value: \(store.state.userState.user?.firstName)") | |
} | |
//: Login Feature | |
struct UserState { | |
var user: User? | |
var loginRequested = false | |
var loginError: ErrorType? | |
} | |
struct User { | |
var username = "" | |
var firstName = "" | |
var lastName = "" | |
} | |
enum UserAction: Action { | |
case requestLogin | |
case userSuccesfullyLoggedIn(user: User) | |
case loginFailed(error: ErrorType) | |
func execute(state: MutableState) -> MutableState { | |
var appState = state | |
var userState = appState.userState | |
switch self { | |
case .requestLogin: | |
userState.loginRequested = true | |
case .userSuccesfullyLoggedIn(let user): | |
userState.loginRequested = false | |
userState.user = user | |
case .loginFailed(let error): | |
userState.loginRequested = false | |
userState.loginError = error | |
} | |
appState.userState = userState | |
return appState | |
} | |
} | |
//: Login Feature Tests | |
func test_request_login_action() { | |
let action = UserAction.requestLogin | |
var state = AppState() | |
state.userState.loginRequested = false | |
let newState = action.execute(state) | |
assert(newState.userState.loginRequested == true) | |
} | |
func test_user_succesfully_logged_in_action() { | |
var testUser = User() | |
testUser.firstName = "Test" | |
testUser.lastName = "User" | |
testUser.username = "testuser" | |
let action = UserAction.userSuccesfullyLoggedIn(user: testUser) | |
var state = AppState() | |
state.userState.user = nil | |
state.userState.loginRequested = true | |
let newState = action.execute(state) | |
assert(newState.userState.user?.firstName == testUser.firstName) | |
assert(newState.userState.user?.lastName == testUser.lastName) | |
assert(newState.userState.user?.username == testUser.username) | |
assert(newState.userState.loginRequested == false) | |
} | |
func test_login_failed_action() { | |
struct TestError: ErrorType {} | |
let action = UserAction.loginFailed(error: TestError()) | |
var state = AppState() | |
state.userState.loginRequested = true | |
let newState = action.execute(state) | |
assert(newState.userState.loginRequested == false) | |
} | |
//: Test Runner | |
test_request_login_action() | |
test_user_succesfully_logged_in_action() | |
test_login_failed_action() | |
test_store_dispatch_queue() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment