Skip to content

Instantly share code, notes, and snippets.

@Dev1an
Last active January 24, 2019 18:35
Show Gist options
  • Select an option

  • Save Dev1an/94106db5caa37e824a7b7c49bf763c40 to your computer and use it in GitHub Desktop.

Select an option

Save Dev1an/94106db5caa37e824a7b7c49bf763c40 to your computer and use it in GitHub Desktop.
An observable CustomStringConvertible, RandomAccess-, RangeReplaceableCollection in 50 lines of swift
// Created by Damiaan on 23/01/2019.
// Copyright © 2019 Devian. All rights reserved.
class SmartCollection<Element> {
class Reference {
private (set) var pointee: Element
init(to element: Element) {
pointee = element
}
fileprivate subscript<Value>(smartPath: WritableKeyPath<Element, Value>) -> Value {
get { return pointee[keyPath: smartPath] }
set {
pointee[keyPath: smartPath] = newValue
}
}
}
private (set) var references = [Reference]()
typealias FieldChangeSubscription = ((_ index: Int, _ changedKeyPath: PartialKeyPath<Element>) -> Void)
typealias ReplaceSubscription = ((_ subrange: Range<Int>, _ newElements: Array<Element>) -> Void)
var preFieldChangeSubscriptions = [FieldChangeSubscription]()
var postFieldChangeSubscriptions = [FieldChangeSubscription]()
var preReplaceSubscriptions = [ReplaceSubscription]()
var postReplaceSubscriptions = [ReplaceSubscription]()
required init() {}
subscript<Value>(_ index: Int, keyPath: WritableKeyPath<Element, Value>) -> Value {
get { return references[index][keyPath] }
set {
let notify: (FieldChangeSubscription)->() = { $0(index, keyPath) }
preFieldChangeSubscriptions.forEach(notify)
references[index][keyPath] = newValue
postFieldChangeSubscriptions.forEach(notify)
}
}
}
extension SmartCollection: RandomAccessCollection {
var startIndex: Int { return references.startIndex }
var endIndex: Int { return references.endIndex}
subscript(_ index: Int) -> Element {
return references[index].pointee
}
}
extension SmartCollection: RangeReplaceableCollection {
func replaceSubrange<C, R>(_ subrange: R, with newElements: C) where C : Collection, R : RangeExpression, Element == C.Element, Int == R.Bound {
let notify: (ReplaceSubscription)->() = { $0(subrange.relative(to: self.references), Array(newElements)) }
preReplaceSubscriptions.forEach(notify)
references.replaceSubrange(subrange, with: newElements.map(Reference.init))
postReplaceSubscriptions.forEach(notify)
}
}
extension SmartCollection: CustomStringConvertible where Element: CustomStringConvertible {
var description: String {
return "SmartCollection " + references.map {$0.pointee.description}.description
}
}
extension SmartCollection.Reference: CustomStringConvertible where Element: CustomStringConvertible {
var description: String {
return "Reference to \(pointee)"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment