Skip to content

Commit

Permalink
Merge pull request #22 from yonekawa/rename_identifier_to_token
Browse files Browse the repository at this point in the history
Use NSUUID for dispatch & subscribe token and refactoring
  • Loading branch information
yonekawa committed Mar 1, 2016
2 parents 1ff57f1 + b038591 commit b6c2b4a
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 97 deletions.
72 changes: 36 additions & 36 deletions SwiftFlux/Dispatcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,72 +9,79 @@
import Foundation
import Result

public typealias DispatchToken = String

public protocol Dispatcher {
func dispatch<T: Action>(action: T, result: Result<T.Payload, T.Error>)
func register<T: Action>(type: T.Type, handler: (Result<T.Payload, T.Error>) -> Void) -> String
func unregister(identifier: String)
func waitFor<T: Action>(identifiers: Array<String>, type: T.Type, result: Result<T.Payload, T.Error>)
func register<T: Action>(type: T.Type, handler: (Result<T.Payload, T.Error>) -> ()) -> DispatchToken
func unregister(dispatchToken: DispatchToken)
func waitFor<T: Action>(dispatchTokens: [DispatchToken], type: T.Type, result: Result<T.Payload, T.Error>)
}

public class DefaultDispatcher: Dispatcher {
private var callbacks: Dictionary<String, AnyObject> = [:]
private var lastDispatchIdentifier = 0
internal enum Status {
case Waiting
case Pending
case Handled
}

private var callbacks: [DispatchToken: AnyObject] = [:]

public init() {}

deinit {
self.callbacks.removeAll()
callbacks.removeAll()
}

public func dispatch<T: Action>(action: T, result: Result<T.Payload, T.Error>) {
self.dispatch(action.dynamicType, result: result)
dispatch(action.dynamicType, result: result)
}

public func register<T: Action>(type: T.Type, handler: (Result<T.Payload, T.Error>) -> Void) -> String {
let nextDispatchIdentifier = "DISPATCH_CALLBACK_\(++self.lastDispatchIdentifier)"
self.callbacks[nextDispatchIdentifier] = DispatchCallback<T>(type: type, handler: handler)
return nextDispatchIdentifier
public func register<T: Action>(type: T.Type, handler: (Result<T.Payload, T.Error>) -> Void) -> DispatchToken {
let nextDispatchToken = NSUUID().UUIDString
callbacks[nextDispatchToken] = DispatchCallback<T>(type: type, handler: handler)
return nextDispatchToken
}

public func unregister(identifier: String) {
self.callbacks.removeValueForKey(identifier)
public func unregister(dispatchToken: DispatchToken) {
callbacks.removeValueForKey(dispatchToken)
}

public func waitFor<T: Action>(identifiers: Array<String>, type: T.Type, result: Result<T.Payload, T.Error>) {
for identifier in identifiers {
guard let callback = self.callbacks[identifier] as? DispatchCallback<T> else { continue }
public func waitFor<T: Action>(dispatchTokens: [DispatchToken], type: T.Type, result: Result<T.Payload, T.Error>) {
for dispatchToken in dispatchTokens {
guard let callback = callbacks[dispatchToken] as? DispatchCallback<T> else { continue }
switch callback.status {
case .Handled:
continue
case .Pending:
// Circular dependency detected while
continue
default:
self.invokeCallback(identifier, type: type, result: result)
invokeCallback(dispatchToken, type: type, result: result)
}
}
}

private func dispatch<T: Action>(type: T.Type, result: Result<T.Payload, T.Error>) {
objc_sync_enter(self)

self.startDispatching(type)
for identifier in self.callbacks.keys {
self.invokeCallback(identifier, type: type, result: result)
startDispatching(type)
for dispatchToken in callbacks.keys {
invokeCallback(dispatchToken, type: type, result: result)
}

objc_sync_exit(self)
}

private func startDispatching<T: Action>(type: T.Type) {
for (identifier, _) in self.callbacks {
guard let callback = self.callbacks[identifier] as? DispatchCallback<T> else { continue }
for (dispatchToken, _) in callbacks {
guard let callback = callbacks[dispatchToken] as? DispatchCallback<T> else { continue }
callback.status = .Waiting
}
}

private func invokeCallback<T: Action>(identifier: String, type: T.Type, result: Result<T.Payload, T.Error>) {
guard let callback = self.callbacks[identifier] as? DispatchCallback<T> else { return }
private func invokeCallback<T: Action>(dispatchToken: DispatchToken, type: T.Type, result: Result<T.Payload, T.Error>) {
guard let callback = callbacks[dispatchToken] as? DispatchCallback<T> else { return }
guard callback.status == .Waiting else { return }

callback.status = .Pending
Expand All @@ -83,20 +90,13 @@ public class DefaultDispatcher: Dispatcher {
}
}

internal class DispatchCallback<T: Action> {
private class DispatchCallback<T: Action> {
let type: T.Type
let handler: (Result<T.Payload, T.Error>) -> Void

var status: DispatchStatus = DispatchStatus.Waiting
let handler: (Result<T.Payload, T.Error>) -> ()
var status = DefaultDispatcher.Status.Waiting

init(type: T.Type, handler: (Result<T.Payload, T.Error>) -> Void) {
init(type: T.Type, handler: (Result<T.Payload, T.Error>) -> ()) {
self.type = type
self.handler = handler
}
}

internal enum DispatchStatus {
case Waiting
case Pending
case Handled
}
}
39 changes: 20 additions & 19 deletions SwiftFlux/Store.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@

import Foundation

public typealias StoreListenerToken = String

public protocol Store : AnyObject {
}

private var EventEmitterObjectKey: UInt8 = 0

extension Store {
public var eventEmitter: EventEmitter {
private var eventEmitter: EventEmitter {
guard let eventEmitter = objc_getAssociatedObject(self, &EventEmitterObjectKey) as? EventEmitter else {
let eventEmitter = DefaultEventEmitter()
objc_setAssociatedObject(self, &EventEmitterObjectKey, eventEmitter, .OBJC_ASSOCIATION_RETAIN)
Expand All @@ -23,12 +25,12 @@ extension Store {
return eventEmitter
}

public func subscribe(handler: () -> ()) -> String {
public func subscribe(handler: () -> ()) -> StoreListenerToken {
return eventEmitter.subscribe(self, handler: handler)
}

public func unsubscribe(identifier: String) {
eventEmitter.unsubscribe(self, identifier: identifier)
public func unsubscribe(listenerToken: StoreListenerToken) {
eventEmitter.unsubscribe(self, listenerToken: listenerToken)
}

public func unsubscribeAll() {
Expand All @@ -43,41 +45,40 @@ extension Store {
public protocol EventEmitter {
func subscribe<T: Store>(store: T, handler: () -> ()) -> String
func unsubscribe<T: Store>(store: T)
func unsubscribe<T: Store>(store: T, identifier: String)
func unsubscribe<T: Store>(store: T, listenerToken: StoreListenerToken)
func emitChange<T: Store>(store: T)
}

public class DefaultEventEmitter: EventEmitter {
private var eventListeners: Dictionary<String, EventListener> = [:]
private var lastListenerIdentifier = 0
private var eventListeners: [StoreListenerToken: EventListener] = [:]

public init() {}
deinit {
self.eventListeners.removeAll()
eventListeners.removeAll()
}

public func subscribe<T: Store>(store: T, handler: () -> ()) -> String {
let nextListenerIdentifier = "EVENT_LISTENER_\(++lastListenerIdentifier)"
self.eventListeners[nextListenerIdentifier] = EventListener(store: store, handler: handler)
return nextListenerIdentifier
public func subscribe<T: Store>(store: T, handler: () -> ()) -> StoreListenerToken {
let nextListenerToken = NSUUID().UUIDString
eventListeners[nextListenerToken] = EventListener(store: store, handler: handler)
return nextListenerToken
}

public func unsubscribe<T: Store>(store: T) {
self.eventListeners.forEach({ (identifier, listener) -> () in
eventListeners.forEach { (token, listener) -> () in
if (listener.store === store) {
self.eventListeners.removeValueForKey(identifier)
eventListeners.removeValueForKey(token)
}
})
}
}

public func unsubscribe<T: Store>(store: T, identifier: String) {
self.eventListeners.removeValueForKey(identifier)
public func unsubscribe<T: Store>(store: T, listenerToken: StoreListenerToken) {
eventListeners.removeValueForKey(listenerToken)
}

public func emitChange<T: Store>(store: T) {
self.eventListeners.forEach({ (_, listener) -> () in
eventListeners.forEach { (_, listener) -> () in
if (listener.store === store) { listener.handler() }
})
}
}
}

Expand Down
3 changes: 1 addition & 2 deletions SwiftFlux/Utils/ReduceStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class ReduceStore<T: Equatable>: StoreBase {
return internalState ?? initialState
}

public func reduce<A: Action>(type: A.Type, reducer: (T, Result<A.Payload, A.Error>) -> T) -> String {
public func reduce<A: Action>(type: A.Type, reducer: (T, Result<A.Payload, A.Error>) -> T) -> DispatchToken {
return self.register(type) { (result) in
let startState = self.state
self.internalState = reducer(self.state, result)
Expand All @@ -30,4 +30,3 @@ public class ReduceStore<T: Equatable>: StoreBase {
}
}
}

15 changes: 7 additions & 8 deletions SwiftFlux/Utils/StoreBase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,21 @@
import Result

public class StoreBase: Store {
private var dispatchIdentifiers: [String] = []
private var dispatchTokens: [DispatchToken] = []

public init() {}

public func register<T: Action>(type: T.Type, handler: (Result<T.Payload, T.Error>) -> ()) -> String {
let identifier = ActionCreator.dispatcher.register(type) { (result) -> () in
public func register<T: Action>(type: T.Type, handler: (Result<T.Payload, T.Error>) -> ()) -> DispatchToken {
let dispatchToken = ActionCreator.dispatcher.register(type) { (result) -> () in
handler(result)
}
dispatchIdentifiers.append(identifier)

return identifier
dispatchTokens.append(dispatchToken)
return dispatchToken
}

public func unregister() {
dispatchIdentifiers.forEach { (identifier) -> () in
ActionCreator.dispatcher.unregister(identifier)
dispatchTokens.forEach { (dispatchToken) -> () in
ActionCreator.dispatcher.unregister(dispatchToken)
}
}
}
4 changes: 2 additions & 2 deletions SwiftFluxTests/DispatcherSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class DispatcherSpec: QuickSpec {
describe("dispatch") {
var results = [String]()
var fails = [String]()
var callbacks = [String]()
var callbacks = [DispatchToken]()

beforeEach { () in
results = []
Expand Down Expand Up @@ -80,7 +80,7 @@ class DispatcherSpec: QuickSpec {

describe("waitFor") {
var results = [String]()
var callbacks = [String]()
var callbacks = [DispatchToken]()
var id1 = "";
var id2 = "";
var id3 = "";
Expand Down
68 changes: 44 additions & 24 deletions SwiftFluxTests/StoreSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,53 +12,73 @@ import Result
import SwiftFlux

class StoreSpec: QuickSpec {
final class TestStore: Store {}
final class TestStore1: Store {}
final class TestStore2: Store {}

override func spec() {
let store = TestStore()
let store1 = TestStore1()
let store2 = TestStore2()

describe("emitChange") {
var unsubscribeIdentifier = ""
var results = [String]()

beforeEach { () in
results = []
store.subscribe { () in
results.append("test 1")
}
unsubscribeIdentifier = store.subscribe { () in
results.append("test 2")
store1.subscribe { () in
results.append("store1")
}
store2.subscribe { () in
results.append("test2 1")
results.append("store2")
}
}

afterEach { () in
store.unsubscribeAll()
store1.unsubscribeAll()
store2.unsubscribeAll()
}

it("should fire event correctly") {
store.emitChange()
store1.emitChange()
expect(results.count).to(equal(1))
expect(results).to(contain("store1"))

store1.emitChange()
expect(results.count).to(equal(2))
expect(results[0]).to(equal("test 2"))
expect(results[1]).to(equal("test 1"))
expect(results).to(contain("store1"))

store.unsubscribe(unsubscribeIdentifier)
store.emitChange()
store2.emitChange()
expect(results.count).to(equal(3))
expect(results[0]).to(equal("test 2"))
expect(results[1]).to(equal("test 1"))
expect(results[2]).to(equal("test 1"))
expect(results).to(contain("store2"))
}
}

store2.emitChange()
expect(results.count).to(equal(4))
expect(results[0]).to(equal("test 2"))
expect(results[1]).to(equal("test 1"))
expect(results[2]).to(equal("test 1"))
expect(results[3]).to(equal("test2 1"))
describe("unsubscribe") {
var results = [String]()
var token = ""

beforeEach { () in
results = []
token = store1.subscribe { () in
results.append("store1")
}
}

afterEach { () in
store1.unsubscribeAll()
store2.unsubscribeAll()
}

it("should unsubscribe collectly") {
store1.emitChange()
expect(results.count).to(equal(1))
expect(results).to(contain("store1"))

results = []

store1.unsubscribe(token)
store1.emitChange()
expect(results.count).to(equal(0))
expect(results).toNot(contain("store1"))
}
}
}
Expand Down
Loading

0 comments on commit b6c2b4a

Please sign in to comment.