var modificationDate: Date var thumbnail: UIImage? ... } var documents: Set<FileMetadata> The set of documents comes from an NSMetadataQuery or a DispatchSource watching the local documents folder.
enum SortOrder { case byModificationDate case byName /// Returns true if `a` should be ordered before `b`. func comparator(a: FileMetadata, b: FileMetadata) -> Bool }
String?, sortOrder: SortOrder) -> [PickerItem] { return ([.newDocument] + documents.sorted(by: sortOrder.comparator) .map { .document($0) } ).filter { $0.matches(searchText) } } We have built the en-re solu-on out of simple composi-ons of standard array and set transforma-ons.
represent muta+ng state. An observable value is an en(ty that • has a ge(er for a value that may change from 1me to 1me • provides an interface for subscribing to the value's incremental change no2fica2ons Basically, it supports both pull- and push-based access to its value
protocol SourceType { associatedtype Value func connect(_ sink: Sink<Value>) -> Connection } class Signal<Value>: SourceType { func connect(_ sink: Sink<Value>) -> Connection func send(_ value: Value) } let s = Signal<Int>() let c = s.connect { print($0) } s.send(42) // Prints "42" c.disconnect() This is just the classic Observer pa2ern.
Change.Value { get } var changes: Source<Change> { get } } This is a bit too abstract. We need to know more about the structure of the value and the details of the change descrip:on to do interes:ng opera:ons on observables.
flavors by the structure of their value types. Value type: T Array<T> Set<T> ------------------------------------------------------------------------------- Protocol name: ObservableScalarType ObservableArrayType ObservableSetType Change type: ScalarChange<T> ArrayChange<T> SetChange<T> Type-lifted: Observable<T> ObservableArray<T> ObservableSet<T> Concrete: Variable<T> ArrayVariable<T> SetVariable<T> Addi$onal flavors (dic$onaries, tree hierarchies etc.) are le6 as an exercise for the reader.
Value { get } var changes: Source<ScalarChange<Value>> { get } } struct ScalarChange<Value>: ChangeType { let old: Value let new: Value init(from old: Value, to new: Value) { self.old = old; self.new = new } func apply(on value: inout Value) { value = new } func merged(with change: ScalarChange) -> ScalarChange { return .init(from: old, to: change.new) } func reversed() -> ScalarChange { return .init(from: new, to: old) } }
= SimpleChange<Value> let signal = Signal<Change>() var value: Value { didSet { signal.send(Change(from: oldValue, to: newValue)) } } var changes: Source<Change> { return signal.source } init(_ value: Value) { self.value = value } } let name = Variable<String>("Fred") let connection = name.changes.connect { c in print("Bye \(c.old), hi \(c.new)!") } name.value = "Barney" // Prints "Bye Fred, hi Barney!" connection.disconnect()
O) -> Observable<O.Value> where O.Value: IntegerArithmetic return BinaryObservable(a, b, +).observable } let a = Variable<Int>(23) let b = Variable<Int>(42) let sum = a + b // Type is Observable<Int> print(sum.value) // Prints "65" a.value = 13 print(sum.value) // Prints "55"
Int { get } subscript(index: Int) -> Element { get } subscript(bounds: Range<Int>) -> Array<Element> { get } var value: Array<Element> { get } var changes: Source<ArrayChange<Element>> { get } } extension ObservableArrayType { var value: Array<Element> { return self[0 ..< count] } subscript(index: Int) -> Element { return self[index ..< index + 1].first! } }
let sum = a + a let c = sum.connect { print("\($0.old) -> \($0.new)") } a.value = 1 // Prints: "0 -> 1", "1 -> 2" a.value = 3 // Prints: "2 -> 4", "4 -> 6" c.disconnect() One general solu,on is to convert change no,fica,ons into a two- phase system — willChange/didChange. (Does this sound familiar?)
class Bookshelf { let books: ArrayVariable<Book> } let b1 = Book("Anathem") let b2 = Book("Cryptonomicon") let shelf: ArrayVariable<Book> = [b1, b2] let titles = shelf.selectEach{$0.title} // Type is ObservableArray<String> let c = titles.changes.connect { _ in print(titles.value) } print(titles.value) // Prints "[Anathem, Cryptonomicon]" b1.title = "Seveneves" // Prints "[Seveneves, Cryptonomicon]" shelf.append(Book("Zodiac")) // Prints "[Seveneves, Cryptonomicon, Zodiac]" shelf.remove(at: 1) // Prints "[Seveneves, Zodiac]" b2.title = "The Diamond Age" // Nothing printed, b2 isn't in shelf c.disconnect()
count: Int { get } func contains(_ element: Element) -> Bool var value: Set<Element> { get } var changes: Source<SetChange<Element>> { get } } struct SetChange<Element: Hashable>: ChangeType { let removed: Set<Element> let inserted: Set<Element> ... }