Last active
October 14, 2021 10:24
-
-
Save chriseidhof/3423e722d1da4e8cce7cfdf85f026ef7 to your computer and use it in GitHub Desktop.
References Blogpost
This file contains 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
//: 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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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