Skip to content

Instantly share code, notes, and snippets.

@rcharlton
Last active May 17, 2017 08:04
Show Gist options
  • Save rcharlton/ac6f8119d137e761f21dc595376df7a2 to your computer and use it in GitHub Desktop.
Save rcharlton/ac6f8119d137e761f21dc595376df7a2 to your computer and use it in GitHub Desktop.
A super-simple Observer pattern.
import Foundation
public final class Observer<T> {
public weak var subject: Observable<T>?
fileprivate var identifier: UInt
fileprivate var isValid: Bool {
return subject != nil
}
fileprivate func invalidate() {
subject = nil
}
fileprivate init(subject: Observable<T>, identifier: UInt) {
self.subject = subject
self.identifier = identifier
}
deinit {
stopObserving()
}
public func stopObserving() {
subject?.removeObserver(self)
}
}
/**
A super-simple value observer.
Not thread-safe.
*/
public final class Observable<T> {
public typealias Closure = (T, T) -> Void
/// The underlying value, which is observed.
public var value: T {
didSet {
observers.forEach { (_, closure) in
closure(oldValue, value)
}
}
}
private var observers = [UInt: Closure]()
/**
Initialise this observable with a value.
*/
public init(_ value: T) {
self.value = value
}
/**
Observe the stored value using the given closure.
The given closure is retained until a corresponding call
to removeObserver is made.
- parameter closure: Invoked with the old and new values
when a change occurs.
- returns: A value identifying the closure.
*/
public func observe(using closure: @escaping Closure) -> Observer<T> {
let identifier = Global.counter.value
Global.counter.increment()
observers[identifier] = closure
return Observer<T>(subject: self, identifier: identifier)
}
/**
Removes a previously subscribed observer.
*/
fileprivate func removeObserver(_ observer: Observer<T>) {
guard observer.isValid else { return }
observers.removeValue(forKey: observer.identifier)
observer.invalidate()
}
}
postfix operator •
/**
A shortcut for the value property.
- returns: the stored value of an Observable.
*/
public postfix func •<T>(a: Observable<T>) -> T {
return a.value
}
infix operator ==>
/**
A shortcut for the observe(using:) method.
*/
@discardableResult public func ==><T>(
subject: Observable<T>,
closure: @escaping Observable<T>.Closure)
-> Observer<T> {
return subject.observe(using: closure)
}
private enum Global {
static var counter = Counter()
}
/**
Basic integer counter; rolls over at MAX_UINT.
*/
private struct Counter {
private(set) var value: UInt = 0
mutating func increment() {
value += 1
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment