Created
October 3, 2015 12:09
-
-
Save DivineDominion/8c78f1cdd327f4fd87f0 to your computer and use it in GitHub Desktop.
Chaining async event handling
This file contains 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 | |
// Application Service, responsible for transactions | |
class CreateGroupedMonitor { | |
// Domain Services: | |
let monitoringService: MonitorFile | |
let monitorMovalService: MoveMonitor | |
init(monitoringService: MonitorFile, groupCreationService: CreateGroup, monitorMovalService: MoveMonitor, updateSchedule: UpdateSchedule, wordCountStrategyRegistry: WordCountStrategyRegistry, monitorRepository: MonitorRepository) { | |
self.monitoringService = monitoringService | |
self.monitorMovalService = monitorMovalService | |
} | |
func storeGroupedMonitor(URL: NSURL, groupID: groupID) { | |
let fileID = FileID() | |
let eventExpectation = subscribeUsing(groupMonitorWhenAdded(groupID)) | |
.passingTest({ $0.fileID == fileID }) | |
.onFailure({ NSLog("Expected FileMonitorAdded event for \(fileID) did not occur.") }) | |
try eventExpectation.fulfilExecuting() { | |
try self.monitorFile(URL: URL, fileID: fileID) | |
} | |
} | |
private func groupMonitorWhenAdded(groupID: GroupID)(event: FileMonitorAdded) { | |
let fileID = event.fileID | |
moveMonitor(fileID, toGroup: groupID) | |
} | |
private func monitorFile(URL URL: LocalURL, fileID: FileID) throws { | |
do { | |
try self.monitoringService.monitorFile(URL: URL, fileID: fileID) | |
} catch let error as FileMonitoringError { | |
switch error { | |
case .CouldNotVendMonitor: | |
log("Could not vend monitor for URL \(URL).") | |
case let .CouldNotAddMonitor(repositoryError: wrappedError): | |
log("Could not add monitor for URL \(URL) (\(error) : \(wrappedError)") | |
} | |
// Rethrow | |
throw error | |
} | |
} | |
private func moveMonitor(fileID: FileID, toGroup groupID: GroupID) { | |
do { | |
try unitOfWork().execute() { | |
self.monitorMovalService.moveMonitorWithFileID(fileID, toGroupWithID: groupID) | |
} | |
} catch let error as NSError { | |
log("Could not move monitor with ID \(fileID) to group \(groupID) (\(error)") | |
} | |
} | |
} |
This file contains 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 Async | |
private func passThroughPredicate<T: DomainEvent>(event: T) -> Bool { | |
return true | |
} | |
func subscribeUsing<T: DomainEvent>(eventPosted: (T) -> Void) -> AutoSubscription<T> { | |
return AutoSubscription(eventPosted: eventPosted) | |
} | |
class AutoSubscription<T: DomainEvent> { | |
private var autoUnsubscribingSubscription: DomainEventSubscription! | |
var delayedExpectation: Async! | |
let eventPosted: (T) -> Void | |
var failureHandler: (() -> Void)? | |
var passesTest: (T) -> Bool = passThroughPredicate | |
init(eventPosted: (T) -> Void) { | |
self.eventPosted = eventPosted | |
self.delayedExpectation = Async.main(after: 120, block: timeUp) | |
// This Async call equals: | |
// let seconds = 120 | |
// let nanoSeconds = Int64(seconds * Double(NSEC_PER_SEC)) | |
// let time = dispatch_time(DISPATCH_TIME_NOW, nanoSeconds) | |
// let _block = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS, block) | |
// dispatch_after(time, queue, _block) | |
self.autoUnsubscribingSubscription = DomainEventPublisher.sharedInstance.subscribe(T.self, usingBlock: processEvent) | |
} | |
private func unsubscribe() { | |
autoUnsubscribingSubscription = .None | |
} | |
private func timeUp() { | |
failureHandler?() | |
} | |
private func processEvent(event: T) { | |
guard passesTest(event) else { | |
return | |
} | |
eventDidOccur(event) | |
} | |
private func eventDidOccur(event: T) { | |
fulfilExpectation() | |
eventPosted(event) | |
} | |
private func fulfilExpectation() { | |
delayedExpectation.cancel() | |
// equals `dispatch_block_cancel(block)` | |
} | |
func fulfilExecuting(block: () throws -> Void) throws { | |
do { | |
try block() | |
} catch let error as NSError { | |
fulfilExpectation() | |
unsubscribe() | |
throw error | |
} | |
} | |
func onFailure(block: () -> Void) -> AutoSubscription { | |
failureHandler = block | |
return self | |
} | |
func passingTest(block: (T) -> Bool) -> AutoSubscription { | |
passesTest = block | |
return self | |
} | |
} |
This file contains 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 | |
protocol DomainEvent { | |
static var eventName: String { get } | |
init(userInfo: UserInfo) | |
func userInfo() -> UserInfo | |
} | |
func notification<T: DomainEvent>(event: T) -> NSNotification { | |
return NSNotification(name: T.eventName, object: nil, userInfo: event.userInfo()) | |
} | |
struct FileMonitorAdded: DomainEvent { | |
static let eventName = "File Monitor Added" | |
let fileID: FileID | |
init(fileID: FileID) { | |
self.fileID = fileID | |
} | |
init(userInfo: UserInfo) { | |
let identifier = userInfo["file_id"] as! String | |
self.fileID = FileID(identifier: identifier) | |
} | |
func userInfo() -> UserInfo { | |
return ["file_id" : fileID.identifier] | |
} | |
} |
This file contains 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 | |
private struct DomainEventPublisherStatic { | |
static var singleton: DomainEventPublisher? = nil | |
static var onceToken: dispatch_once_t = 0 | |
} | |
class DomainEventPublisher { | |
class var sharedInstance: DomainEventPublisher { | |
if !hasValue(DomainEventPublisherStatic.singleton) { | |
dispatch_once(&DomainEventPublisherStatic.onceToken) { | |
self.setSharedInstance(DomainEventPublisher()) | |
} | |
} | |
return DomainEventPublisherStatic.singleton! | |
} | |
class func resetSharedInstance() { | |
DomainEventPublisherStatic.singleton = nil | |
DomainEventPublisherStatic.onceToken = 0 | |
} | |
class func setSharedInstance(instance: DomainEventPublisher) { | |
DomainEventPublisherStatic.singleton = instance | |
} | |
let notificationCenter: NSNotificationCenter | |
convenience init() { | |
self.init(notificationCenter: NSNotificationCenter.defaultCenter()) | |
} | |
init(notificationCenter: NSNotificationCenter) { | |
self.notificationCenter = notificationCenter | |
} | |
//MARK: - | |
//MARK: Event Publishing and Subscribing | |
func publish<T: DomainEvent>(event: T) { | |
let notificationCenter = self.notificationCenter | |
let notif = notification(event) | |
dispatch_async(dispatch_get_main_queue()) { | |
notificationCenter.postNotification(notif) | |
} | |
} | |
func subscribe<T: DomainEvent>(eventKind: T.Type, usingBlock block: (T) -> Void) -> DomainEventSubscription { | |
let mainQueue = NSOperationQueue.mainQueue() | |
return self.subscribe(eventKind, queue: mainQueue, usingBlock: block) | |
} | |
func subscribe<T: DomainEvent>(eventKind: T.Type, queue: NSOperationQueue, usingBlock block: (T) -> Void) -> DomainEventSubscription { | |
let eventName: String = T.eventName | |
let observer = notificationCenter.addObserverForName(eventName, object: nil, queue: queue) { | |
notification in | |
let userInfo = notification.userInfo! | |
let event: T = T(userInfo: userInfo) | |
block(event) | |
} | |
return DomainEventSubscription(observer: observer, eventPublisher: self) | |
} | |
func unsubscribe(subscriber: AnyObject) { | |
notificationCenter.removeObserver(subscriber) | |
} | |
} |
This file contains 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 | |
class DomainEventSubscription { | |
let observer: NSObjectProtocol | |
let eventPublisher: DomainEventPublisher | |
init(observer: NSObjectProtocol, eventPublisher: DomainEventPublisher) { | |
self.observer = observer | |
self.eventPublisher = eventPublisher | |
} | |
func unsubscribe() { | |
eventPublisher.unsubscribe(observer) | |
} | |
deinit { | |
unsubscribe() | |
} | |
} |
This file contains 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
// | |
// Minimalistic domain code, containing just basic IDs so you know what's going on in client | |
// | |
struct GroupID { | |
let identifier: String | |
init() { | |
self.identifier = NSUUID().UUIDString | |
} | |
} | |
struct FileID { | |
let identifier: String | |
init() { | |
self.identifier = NSUUID().UUIDString | |
} | |
} |
This file contains 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 CoreData | |
var managedObjectContext: NSManagedObjectContext? | |
func unitOfWork() -> UnitOfWork { | |
precondition(managedObjectContext != nil, "managedObjectContext must be set up") | |
return UnitOfWork(managedObjectContext: managedObjectContext!) | |
} | |
class UnitOfWork { | |
let managedObjectContext: NSManagedObjectContext | |
init(managedObjectContext: NSManagedObjectContext) { | |
self.managedObjectContext = managedObjectContext | |
} | |
/// Asynchronously execute `closure` to sequentially perform transactions. | |
func execute(closure: () throws -> Void) throws { | |
var error: NSError? | |
managedObjectContext.performBlock { | |
do { | |
try closure() | |
} catch let executionError as NSError { | |
error = executionError | |
return | |
} | |
do { | |
try self.managedObjectContext.save() | |
} catch let coreDataError as NSError { | |
error = coreDataError | |
} | |
} | |
if let error = error { | |
throw error | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment