Skip to content

Instantly share code, notes, and snippets.

@eliperkins
Created September 10, 2015 22:42
Show Gist options
  • Save eliperkins/b7dd49256437dea3da36 to your computer and use it in GitHub Desktop.
Save eliperkins/b7dd49256437dea3da36 to your computer and use it in GitHub Desktop.
//: 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