Skip to content

Instantly share code, notes, and snippets.

@blixt
Last active August 27, 2015 16:44
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 blixt/c17a944c889da0ff3021 to your computer and use it in GitHub Desktop.
Save blixt/c17a944c889da0ff3021 to your computer and use it in GitHub Desktop.
Why is c3 retained? (Solution: https://gist.github.com/blixt/08434b74f0c043f83fcb)
// Update: See a solution here:
// https://gist.github.com/blixt/08434b74f0c043f83fcb
// (NSMapTable holds onto the weak keys longer than I expected)
import Foundation
private class ClosureWrapper<EventData> {
typealias EventHandler = (EventData) -> Void
let closure: EventHandler
init(_ closure: EventHandler) {
self.closure = closure
}
}
class Event<EventData> {
typealias EventHandler = (EventData) -> Void
private var listeners = NSMapTable.weakToStrongObjectsMapTable()
/// Adds an event listener, notifying the provided method when the event is emitted.
func addListener<ListenerType : AnyObject>(listener: ListenerType, method: (ListenerType) -> EventHandler) {
let closure = {
[weak listener] (data: EventData) in
guard let listener = listener else {
// Listener has been garbage collected.
return
}
method(listener)(data)
}
listeners.setObject(ClosureWrapper(closure), forKey: listener)
}
func removeListener(listener: AnyObject) {
listeners.removeObjectForKey(listener)
}
// Publishes the specified data to all listeners via the global utility dispatch queue.
func emit(data: EventData) {
for wrapper in listeners.objectEnumerator()! {
guard let wrapper = wrapper as? ClosureWrapper<EventData> else {
continue
}
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)) {
wrapper.closure(data)
}
}
}
}
class Player {
// A simple tuple to demonstrate multi-value events (could also be a struct/class of course).
typealias TrackData = (file: String, duration: Int)
// An event with no additional information.
let paused = Event<Void>()
// Another event that emits TrackData.
let trackChanged = Event<TrackData>()
// Demo function to show an event being emitted.
func emitStuff() {
// Emit the track changed event to all listeners.
trackChanged.emit((file: "song1.mp3", duration: 123))
// No need to pass in data if the event data type is Void.
paused.emit()
}
}
// MARK: - Demo
class MyClass {
let name: String
init(name: String, player: Player) {
self.name = name
// Always listen for track changes.
player.trackChanged.addListener(self, method: MyClass.handleTrackChanged)
}
// Events with event type Void don't require any argument in the handler.
func handlePause() {
print("handling pause in \(name)!")
}
// Note that tuples can be expanded in the handler signature, which makes the code cleaner.
func handleTrackChanged(file: String, duration: Int) {
print("handling track changed to \(file) (\(duration) sec) in \(name)!")
}
}
let player = Player()
// Create some instances which will be listening to events.
let c1: MyClass = MyClass(name: "One", player: player)
let c2: MyClass = MyClass(name: "Two", player: player)
var c3: MyClass? = MyClass(name: "Three", player: player)
// You can add listeners to individual objects like this.
player.paused.addListener(c2, method: MyClass.handlePause)
// This is how you remove a listener explicitly.
player.trackChanged.removeListener(c2)
// BUG: c3 is still retained after this because NSMapTable holds onto the keys.
c3 = nil
// Emit the event to all the listeners.
player.emitStuff()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment