Last active
August 29, 2015 14:27
-
-
Save flyinghyrax/a07c53544766ebfbb8f4 to your computer and use it in GitHub Desktop.
Copy into a playground and open the debug area with Cmd+Shift+Y to see output
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
import Foundation | |
/* | |
Demonstrates creating and avoiding retention cycles in callbacks | |
*/ | |
/* UTILITIES */ | |
/// Wraps a something in a class | |
class Box<T> { | |
var value: T | |
init(value: T) { | |
self.value = value | |
} | |
} | |
/* OBSERVABLE */ | |
typealias Listener = () -> Void | |
typealias ListenerBox = Box<Listener> | |
/// Base class for notifications | |
class Observable: NSObject { | |
private var listeners = [ListenerBox]() | |
func subscribe(listener: Listener) -> AnyObject { | |
let box = Box(value: listener) | |
listeners.append(box) | |
return box | |
} | |
func unsubscribe(listener: AnyObject) { | |
for idx in 0..<listeners.count { | |
// this is why we wrap the callbacks in a class... | |
if listeners[idx] === listener { | |
listeners.removeAtIndex(idx) | |
// no break, in case you were silly and registered more than once | |
} | |
} | |
} | |
func notifyObservers() { | |
for box in listeners { | |
box.value() | |
} | |
} | |
} | |
class ConcreteObservable: Observable { | |
static let instance = ConcreteObservable() | |
func change() { | |
print("< Observable changed >") | |
notifyObservers() | |
} | |
} | |
let observable = ConcreteObservable.instance | |
/* OBSERVERS */ | |
protocol Observer { | |
func watch() | |
} | |
class ConcreteObserver { | |
init() { | |
print("| \(self.dynamicType) created |") | |
} | |
func shout() { | |
print("> Shout! \(self.dynamicType) <") | |
} | |
deinit { | |
print("| \(self.dynamicType) deallocated |") | |
} | |
} | |
/** | |
Demonstrates a retention cycle. | |
This will never be deallocated unless it explictly unsubscribes from the Observable | |
*/ | |
class ConcreteObserverBad: ConcreteObserver, Observer { | |
func watch() { | |
ConcreteObservable.instance.subscribe({ | |
self.shout() | |
}) | |
} | |
} | |
/** | |
Uses a weak reference to self in the callback to avoid a cycle. | |
*/ | |
class ConcreteObserverBetter: ConcreteObserver, Observer { | |
func watch() { | |
ConcreteObservable.instance.subscribe({[weak self] in | |
print("... in better observer callback") | |
self?.shout() | |
}) | |
} | |
} | |
class ConcreteObserverBest: ConcreteObserver, Observer { | |
var listener: AnyObject? | |
func watch() { | |
listener = ConcreteObservable.instance.subscribe({[weak self] in | |
print("...in best observer callback") | |
self?.shout() | |
}) | |
} | |
deinit { | |
listener.flatMap({ ConcreteObservable.instance.unsubscribe($0) }) | |
} | |
} | |
/* Demonstrate the retention cycle... */ | |
print("") | |
var observer: Observer? = ConcreteObserverBad() | |
// no-op, as expected | |
observable.change() | |
observer!.watch() | |
// Shouts, as expected | |
observable.change() | |
// notice that deinit is never called: the observer is not deallocated | |
observer = nil | |
// notice that the callback is still executed, even though we have nilled our only reference to the object that registered that callback! | |
observable.change() | |
/* Now without the cycle */ | |
print("") | |
observer = ConcreteObserverBetter() | |
observer!.watch() | |
// The clean observer is notified as expected, but so is the old one, which was never | |
// deinitialized and never explicitly unsubscribed | |
observable.change() | |
// the clean observer is deallocated | |
observer = nil | |
// now only the bad observer from earlier is notified. BUT | |
// the better observers callback is still being called! it never explicitely unsubscribed | |
// so the observable still has the callback. But its reference to its enclosing self was | |
// weak, so the class method (shout) is not called. | |
observable.change() | |
/* NOW with weak reference AND unsubscribe */ | |
print("") | |
observer = ConcreteObserverBest() | |
observer!.watch() | |
observable.change() | |
observer = nil | |
observable.change() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment