Created
September 10, 2015 22:42
-
-
Save eliperkins/b7dd49256437dea3da36 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
//: Playground - noun: a place where people can play | |
import UIKit | |
struct Todo { | |
let id: NSUUID | |
let text: String | |
let complete: Bool | |
} | |
struct TodoActions { | |
static func create(text: String) { | |
AppDispatcher.dispatch(AppAction(source: "VIEW", action: Action.Create(text))) | |
} | |
static func destroy(todo: Todo) { | |
AppDispatcher.dispatch(AppAction(source: "VIEW", action: Action.Destroy(todo.id))) | |
} | |
static func update(id: NSUUID, text: String? = nil, complete: Bool? = nil) { | |
AppDispatcher.dispatch(AppAction(source: "VIEW", action: Action.Update(id, text, complete))) | |
} | |
} | |
typealias Token = String | |
protocol Dispatcher { | |
typealias ValueType | |
/// Registers a callback to be invoked with every dispatched payload. Returns a token that can be used with waitFor(). | |
static func register(callback: (ValueType -> Void)) -> Token | |
/// Removes a callback based on its token. | |
static func unregister(token: Token) | |
/// Waits for the callbacks specified to be invoked before continuing execution of the current callback. This method should only be used by a callback in response to a dispatched payload. | |
static func waitFor(tokens: [Token]) | |
/// Dispatches a payload to all registered callbacks. | |
static func dispatch(payload: ValueType) | |
/// Is this Dispatcher currently dispatching. | |
static func isDispatching() -> Bool | |
} | |
// TODO: Can we use Generics and MirrorType here to prevent this from being custom for every type? | |
enum Action { | |
case Create(String) | |
case Update(NSUUID, String?, Bool?) | |
case Destroy(NSUUID) | |
} | |
struct AppAction { | |
let source: String | |
let action: Action | |
} | |
struct DispatchCallback<T> { | |
let callback: (T -> Void) | |
let token: Token | |
} | |
class AppDispatcher: Dispatcher { | |
typealias ValueType = AppAction | |
private static var dispatching: Bool = false | |
private static var callbacks: [DispatchCallback<ValueType>] = [] | |
static func register(callback: (ValueType -> Void)) -> Token { | |
let dispatchCallback = DispatchCallback(callback: callback, token: NSUUID().UUIDString) | |
callbacks.append(dispatchCallback) | |
return dispatchCallback.token | |
} | |
static func unregister(token: Token) { | |
guard let callbackIndex = callbacks.indexOf({ $0.token == token }) else { return } | |
callbacks.removeAtIndex(callbackIndex) | |
} | |
static func waitFor(tokens: [Token]) { | |
// todo | |
} | |
static func dispatch(payload: ValueType) { | |
dispatching = true | |
callbacks.forEach { | |
$0.callback(payload) | |
} | |
dispatching = false | |
} | |
static func isDispatching() -> Bool { | |
return dispatching | |
} | |
static func handleViewAction(action: Action) { | |
dispatch(AppAction(source: "VIEW_ACTION", action: action)) | |
} | |
} | |
class TodoStore { | |
private var _todos: [Todo] | |
private var changeListeners: [(TodoStore -> Void)] | |
var todos: [Todo] { | |
return _todos | |
} | |
func addChangeListener(callback: (TodoStore -> Void)) -> Int { | |
changeListeners.append(callback) | |
return changeListeners.count - 1 | |
} | |
func removeChangeListener(index: Int) { | |
changeListeners.removeAtIndex(index) | |
} | |
private var token: Token = "" | |
init() { | |
self._todos = [] | |
self.changeListeners = [] | |
let token = AppDispatcher.register { | |
payload in | |
switch payload.action { | |
case .Create(let text): | |
self._todos.append(Todo(id: NSUUID(), text: text, complete: false)) | |
self.emit() | |
case .Update(let id, let text, let completed): | |
guard let todoIndex = self._todos.indexOf({ $0.id == id }) else { return } | |
let todo = self._todos[todoIndex] | |
self._todos.removeAtIndex(todoIndex) | |
let newTodo = Todo(id: id, text: text ?? todo.text, complete: completed ?? todo.complete) | |
self._todos.insert(newTodo, atIndex: todoIndex) | |
self.emit() | |
case .Destroy(let id): | |
guard let todoIndex = self._todos.indexOf({ $0.id == id }) else { return } | |
self._todos.removeAtIndex(todoIndex) | |
self.emit() | |
} | |
} | |
self.token = token | |
} | |
private func emit() { | |
self.changeListeners.forEach { | |
$0(self) | |
} | |
} | |
} | |
var todoStore = TodoStore() | |
@objc class App: NSObject { | |
private var listenerIndex: Int = 0 | |
let tableView = UITableView(frame: CGRect(x: 0, y: 0, width: 300, height: 300), style: .Plain) | |
var state = todoStore.todos { | |
didSet { | |
tableView.reloadData() | |
} | |
} | |
override init() { | |
super.init() | |
tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Cell") | |
tableView.dataSource = self | |
tableView.delegate = self | |
listenerIndex = todoStore.addChangeListener { [weak self] store in | |
self?.state = store.todos | |
} | |
} | |
deinit { | |
todoStore.removeChangeListener(listenerIndex) | |
} | |
} | |
extension App: UITableViewDataSource, UITableViewDelegate { | |
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { | |
return state.count | |
} | |
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { | |
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) | |
cell.textLabel?.text = state[indexPath.row].text | |
return cell | |
} | |
} | |
let app = App() | |
app.tableView | |
TodoActions.create("his") | |
TodoActions.create("hi") | |
app.tableView | |
TodoActions.destroy(todoStore.todos.first!) | |
if let todo = todoStore.todos.first { | |
TodoActions.update(todo.id, text: "Whoo!") | |
TodoActions.destroy(todo) | |
TodoActions.update(todo.id, text: "Whoo!") | |
} | |
app.tableView |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment