Skip to content

Instantly share code, notes, and snippets.

@DivineDominion
Created October 3, 2015 12:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DivineDominion/8c78f1cdd327f4fd87f0 to your computer and use it in GitHub Desktop.
Save DivineDominion/8c78f1cdd327f4fd87f0 to your computer and use it in GitHub Desktop.
Chaining async event handling
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)")
}
}
}
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
}
}
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]
}
}
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)
}
}
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()
}
}
//
// 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
}
}
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