Skip to content

Instantly share code, notes, and snippets.

@monyschuk
Last active June 8, 2016 21:25
Show Gist options
  • Save monyschuk/8fd41ce9befa211c73018bea74e6aec0 to your computer and use it in GitHub Desktop.
Save monyschuk/8fd41ce9befa211c73018bea74e6aec0 to your computer and use it in GitHub Desktop.
Strongly typed EventManager alternative to NSNotificationCenter in Swift
import Foundation
/**
EventType serves as a strongly typed alternative to NSNotification and its name property.
To create the equivalent of a simple notification with no associated information, declare
an empty struct conforming to EventType:
struct AppDataDidRefreshEvent: EventType {}
To associate information with the event - akin to adding custom key-value pairs to a notification's
userInfo dictionary, simply add properties to the structure:
struct UserLoginEvent: EventType {
let name: String
}
Unlike NSNotification, events are identified through their declared type, rather than by a string name.
To subscribe to a `UserLoginEvent` for example. Implement a target-action method on the observer which
takes the type as an argument:
class Greeter {
func greetUser(loginEvent: UserLoginEvent) {
print("Hello there, \(loginEvent.name)")
}
}
var greeter = Greeter()
EventDispatcher.global.addListener(greeter, action: Greeter.greetUser)
Both target-action and closure style listeners are supported for events,
but target-action listeners can be safer becasue they eliminate the risk
of introducing unexpected retain cycles between listeners and their
listening code.
*/
public protocol EventType {}
/**
EventDispatcher's listen... methods return Disposables. Calling dispose
on one of these objects unsubscribes the listener from the event.
Note that target-action style listeners are automatically unsubscribed
from events when the target is deallocated, while block based listeners are
not.
*/
public protocol Disposable {
func dispose()
}
/**
EventDispatcher is the event systems analog to NSNotificationCenter. You can use its singleton - `EventDispatcher.global`
much as you would use `NSNotificationCenter.defaultCenter()`, or create custom dispatchers scoped, for example, to a particular
document.
*/
public final class EventDispatcher {
public static var global = EventDispatcher()
public func post(event: EventType) {
var listened = [EventListener]()
for obj in listeners {
if obj.isValid {
obj.listen(event)
listened.append(obj)
}
}
listeners = listened
}
private var listeners = [EventListener]()
@warn_unused_result
public func addListener<U: EventType>(action: U->()) -> Disposable {
let listener = ClosureEventListener(manager: self, action: action)
listeners.append(listener)
return listener
}
public func addListener<T: AnyObject, U: EventType>(target: T, action: T->U->()) -> Disposable {
let listener = TargetActionEventListener(manager: self, target: target, action: action)
listeners.append(listener)
return listener
}
}
// abstract event listener
private protocol EventListener: class {
var isValid: Bool { get }
func listen(event: EventType)
}
// a closure-based listener
private final class ClosureEventListener<U>: EventListener, Disposable {
let action: U->()
unowned var manager: EventDispatcher
init(manager: EventDispatcher, action: U->()) {
self.manager = manager
self.action = action
}
func dispose() {
manager.listeners =
manager.listeners.filter { $0 !== self }
}
var isValid: Bool {
return true
}
func listen(event: EventType) {
if let event = event as? U {
action(event)
}
}
}
// a target-action style listener
private final class TargetActionEventListener<T: AnyObject, U>: EventListener, Disposable {
weak var target: T?
let action: T->U->()
unowned var manager: EventDispatcher
init(manager: EventDispatcher, target: T, action: T->U->()) {
self.target = target
self.action = action
self.manager = manager
}
func dispose() {
manager.listeners =
manager.listeners.filter { $0 !== self }
}
var isValid: Bool {
return target != nil
}
func listen(event: EventType) {
if let target = target, event = event as? U {
action(target)(event)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment