Last active
August 27, 2015 16:44
-
-
Save blixt/c17a944c889da0ff3021 to your computer and use it in GitHub Desktop.
Why is c3 retained? (Solution: https://gist.github.com/blixt/08434b74f0c043f83fcb)
This file contains hidden or 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
// 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