Skip to content

Instantly share code, notes, and snippets.

@rnapier
Last active January 27, 2018 20:45
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rnapier/58e00f3c3260950be0dc to your computer and use it in GitHub Desktop.
Save rnapier/58e00f3c3260950be0dc to your computer and use it in GitHub Desktop.
Observable sketch
import Foundation
typealias ObserverRemover = () -> Void
/*! An observable value
An `Observable` wraps any value. If you add an observer handler, then every time the value is set, your handler will be
called with the new value. Adding an observer returns a closure that is used to remove the observer. Note that the handler
is called every time the value is set, even if this does not change the value. If you only want the handler to be called
when the value changes, see `CoalescingObservable`.
*/
class Observable<T> {
var value: T {
didSet {
notifyAllObservers()
}
}
fileprivate var observers: [UUID: ((T) -> Void)] = [:]
/*! Adds an observer handler, returning a closure that will remove the observer */
func addObserver(_ didSet: @escaping ((T) -> Void)) -> (ObserverRemover) {
let identifier = UUID()
observers[identifier] = didSet
return { [weak self] in
self?.removeObserver(identifier)
}
}
fileprivate func removeObserver(_ identifier: UUID) {
observers[identifier] = nil
}
init(_ value: T) {
self.value = value
}
/*! Generally used internally, but may be used to "prime" all observers with the current value. */
func notifyAllObservers() {
for observer in observers.values {
observer(value)
}
}
}
extension Observable: CustomStringConvertible {
var description: String {
return "\(value)"
}
}
/*! An `Observable` that only fires notifications when the value changes (rather than every time it is set). */
final class CoalescingObservable<T: Equatable>: Observable<T> {
override var value: T {
didSet {
if oldValue != value {
notifyAllObservers()
}
}
}
}
/*! Helper extension for observers of `Observable`
By conforming to the `Observer` protocol, it is easier to handle the common case of observing several things at one time, and
removing all the observations together.
*/
protocol Observer: class {
var observerRemovers: [ObserverRemover] { get set }
}
extension Observer {
/*! Adds a new observation, updating the object's list of observations to remove later. */
func observe<T>(_ observable: Observable<T>, didSet: @escaping ((T) -> Void)) {
observerRemovers.append(observable.addObserver(didSet))
}
/// Removes all observations registered in observerRemovers (by calling `observe` or by adding them directly to the array).
func removeAllObservations() {
for remover in observerRemovers {
remover()
}
observerRemovers.removeAll()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment