Skip to content

Instantly share code, notes, and snippets.

@chriseidhof
Last active October 14, 2021 10:24
Show Gist options
  • Star 17 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save chriseidhof/3423e722d1da4e8cce7cfdf85f026ef7 to your computer and use it in GitHub Desktop.
Save chriseidhof/3423e722d1da4e8cce7cfdf85f026ef7 to your computer and use it in GitHub Desktop.
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)
Copy link

ghost 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
Copy link
Author

Yes, absolutely!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment