Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
References Blogpost
//: Playground - noun: a place where people can play
import Foundation
final class Disposable {
private let dispose: () -> ()
init(_ dispose: @escaping () -> ()) {
self.dispose = dispose
}
deinit {
dispose()
}
}
final class Ref<A> {
typealias Observer = (A) -> ()
private let _get: () -> A
private let _set: (A) -> ()
private let _addObserver: (@escaping Observer) -> Disposable
var value: A {
get {
return _get()
}
set {
_set(newValue)
}
}
init(get: @escaping () -> A, set: @escaping (A) -> (), addObserver: @escaping (@escaping Observer) -> Disposable) {
_get = get
_set = set
_addObserver = addObserver
}
func addObserver(observer: @escaping Observer) -> Disposable {
return _addObserver(observer)
}
}
extension Ref {
convenience init(initialValue: A) {
var observers: [Int: Observer] = [:]
var theValue = initialValue {
didSet {
observers.values.forEach { $0(theValue) }
}
}
var freshId = (Int.min...).makeIterator()
let get = { theValue }
let set = { newValue in
theValue = newValue
}
let addObserver = { (newObserver: @escaping Observer) -> Disposable in
let id = freshId.next()!
observers[id] = newObserver
return Disposable {
observers[id] = nil
}
}
self.init(get: get, set: set, addObserver: addObserver)
}
}
extension Ref {
subscript<B>(keyPath: WritableKeyPath<A,B>) -> Ref<B> {
let parent = self
return Ref<B>(get: { parent._get()[keyPath: keyPath] }, set: {
var oldValue = parent.value
oldValue[keyPath: keyPath] = $0
parent._set(oldValue)
}, addObserver: { observer in
parent.addObserver { observer($0[keyPath: keyPath]) }
})
}
}
extension Ref where A: MutableCollection {
subscript(index: A.Index) -> Ref<A.Element> {
return Ref<A.Element>(get: { self._get()[index] }, set: { newValue in
var old = self.value
old[index] = newValue
self._set(old)
}, addObserver: { observer in
self.addObserver { observer($0[index]) }
})
}
}
struct History<A> {
private let initialValue: A
private var history: [A] = []
private var redoStack: [A] = []
var value: A {
get {
return history.last ?? initialValue
}
set {
history.append(newValue)
redoStack = []
}
}
init(initialValue: A) {
self.initialValue = initialValue
}
mutating func undo() {
guard let item = history.popLast() else { return }
redoStack.append(item)
}
mutating func redo() {
guard let item = redoStack.popLast() else { return }
history.append(item)
}
}
struct Address {
var street: String
}
struct Person {
var name: String
var addresses: [Address]
}
typealias Addressbook = [Person]
let source: Ref<History<Addressbook>> = Ref(initialValue: History(initialValue: []))
let addressBook: Ref<Addressbook> = source[\.value]
addressBook.value.append(Person(name: "Test", addresses: []))
addressBook[0].value.name = "New Name"
print(addressBook[0].value)
source.value.undo()
print(addressBook[0].value)
source.value.redo()
var twoPeople: Ref<Addressbook> = Ref(initialValue:
[Person(name: "One", addresses: []),
Person(name: "Two", addresses: [])])
let p0 = twoPeople[0]
twoPeople.value.removeFirst()
print(p0.value)
@rommex73

This comment has been minimized.

Copy link

rommex73 commented Sep 24, 2019

Chris, you mentioned in the post that the print from line 147 has weird behavior.
Yes, the problem with the index captured in the line 84 can lead to even more drastic consequences:

let p1 = twoPeople[1]
twoPeople.value.removeFirst() // "Fatal error: Index out of range" -- p1 points to element with index 1 that doesn't exist anymore

@chriseidhof

This comment has been minimized.

Copy link
Owner Author

chriseidhof commented Oct 9, 2019

Yes, absolutely!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.