Repository: lorentey/GlueKit Branch: master Commit: a5766bb11eb2 Files: 192 Total size: 995.4 KB Directory structure: gitextract_rs09iz2z/ ├── .codecov.yml ├── .gitignore ├── .gitmodules ├── .swift-version ├── .travis.yml ├── Cartfile ├── Cartfile.resolved ├── Demo.playground/ │ ├── Pages/ │ │ └── Untitled Page.xcplaygroundpage/ │ │ ├── Contents.swift │ │ └── timeline.xctimeline │ └── contents.xcplayground ├── Documentation/ │ ├── Language Enhancements.md │ └── Overview.md ├── GlueKit.podspec ├── GlueKit.xcodeproj/ │ ├── project.pbxproj │ ├── project.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ └── GlueKit.xcscmblueprint │ └── xcshareddata/ │ ├── xcbaselines/ │ │ ├── BB351AFB1DB81E67005F083F.xcbaseline/ │ │ │ ├── EBF79DD4-EE66-4D33-B51C-EE37857B70A1.plist │ │ │ └── Info.plist │ │ └── BBB55BE11C8FD1C60050DDA9.xcbaseline/ │ │ ├── DD663408-0BA9-46F9-868F-F1570927CA52.plist │ │ └── Info.plist │ └── xcschemes/ │ ├── GlueKit-PerformanceTests.xcscheme │ ├── GlueKit-iOS.xcscheme │ ├── GlueKit-macOS.xcscheme │ ├── GlueKit-tvOS.xcscheme │ └── GlueKit-watchOS.xcscheme ├── GlueKit.xcworkspace/ │ ├── contents.xcworkspacedata │ └── xcshareddata/ │ └── GlueKit.xcscmblueprint ├── LICENSE.md ├── Package.swift ├── README.md ├── Sources/ │ ├── Abstract.swift │ ├── AccumulatedSource.swift │ ├── ArrayBasedTableViewDataSource.swift │ ├── ArrayChange.swift │ ├── ArrayChangeSeparation.swift │ ├── ArrayConcatenation.swift │ ├── ArrayFilteringIndexmap.swift │ ├── ArrayFilteringOnObservableBool.swift │ ├── ArrayFilteringOnPredicate.swift │ ├── ArrayFolding.swift │ ├── ArrayGatheringSource.swift │ ├── ArrayMappingForArrayField.swift │ ├── ArrayMappingForValue.swift │ ├── ArrayMappingForValueField.swift │ ├── ArrayReference.swift │ ├── ArrayVariable.swift │ ├── BracketingSource.swift │ ├── BufferedArray.swift │ ├── BufferedSet.swift │ ├── BufferedSource.swift │ ├── BufferedValue.swift │ ├── CADisplayLink Extensions.swift │ ├── Change.swift │ ├── ChangesSource.swift │ ├── CompositeObservable.swift │ ├── CompositeUpdatable.swift │ ├── ComputedUpdatable.swift │ ├── Connect.swift │ ├── Connector.swift │ ├── DependentValue.swift │ ├── DispatchSource.swift │ ├── DistinctUnion.swift │ ├── DistinctValue.swift │ ├── Info.plist │ ├── Locks.swift │ ├── MergedSource.swift │ ├── NSButton Glue.swift │ ├── NSControl Glue.swift │ ├── NSNotificationCenter Support.swift │ ├── NSObject Glue.swift │ ├── NSPopUpButton Glue.swift │ ├── NSTextField Glue.swift │ ├── ObservableArray.swift │ ├── ObservableContains.swift │ ├── ObservableSet.swift │ ├── ObservableType.swift │ ├── ObservableValue.swift │ ├── OwnedSink.swift │ ├── RefList.swift │ ├── Reference.swift │ ├── SetChange.swift │ ├── SetFilteringOnObservableBool.swift │ ├── SetFilteringOnPredicate.swift │ ├── SetFolding.swift │ ├── SetGatheringSource.swift │ ├── SetMappingBase.swift │ ├── SetMappingForArrayField.swift │ ├── SetMappingForSequence.swift │ ├── SetMappingForSetField.swift │ ├── SetMappingForValue.swift │ ├── SetMappingForValueField.swift │ ├── SetReference.swift │ ├── SetSortingByComparableField.swift │ ├── SetSortingByComparator.swift │ ├── SetSortingByMappingToComparable.swift │ ├── SetSortingByMappingToObservableComparable.swift │ ├── SetVariable.swift │ ├── Signal.swift │ ├── SimpleSources.swift │ ├── Sink.swift │ ├── Source.swift │ ├── TimerSource.swift │ ├── TransactionalThing.swift │ ├── TransformedSink.swift │ ├── TransformedSource.swift │ ├── TwoWayBinding.swift │ ├── Type Helpers.swift │ ├── UIBarButtonItem Extensions.swift │ ├── UIControl Glue.swift │ ├── UIDevice Glue.swift │ ├── UIGestureRecognizer Glue.swift │ ├── UILabel Glue.swift │ ├── UISearchBar Glue.swift │ ├── UISwitch Glue.swift │ ├── UpdatableArray.swift │ ├── UpdatableSet.swift │ ├── UpdatableValue.swift │ ├── Update.swift │ ├── ValueChange.swift │ ├── ValueMappingForArrayField.swift │ ├── ValueMappingForSetField.swift │ ├── ValueMappingForSourceField.swift │ ├── ValueMappingForValue.swift │ ├── ValueMappingForValueField.swift │ ├── ValueReference.swift │ └── Variable.swift ├── Tests/ │ ├── GlueKitTests/ │ │ ├── AnySinkTests.swift │ │ ├── AnySourceTests.swift │ │ ├── ArrayBufferingTests.swift │ │ ├── ArrayChangeSeparationTests.swift │ │ ├── ArrayChangeTests.swift │ │ ├── ArrayConcatenationTests.swift │ │ ├── ArrayFilteringTests.swift │ │ ├── ArrayFoldingTests.swift │ │ ├── ArrayMappingTests.swift │ │ ├── ArrayModificationTests.swift │ │ ├── ArrayReferenceTests.swift │ │ ├── ArrayVariableTests.swift │ │ ├── Bookshelf.swift │ │ ├── BracketingSourceTests.swift │ │ ├── BufferedSourceTests.swift │ │ ├── ChangeTests.swift │ │ ├── ChangesSourceTests.swift │ │ ├── CombinedObservableTests.swift │ │ ├── CombinedUpdatableTests.swift │ │ ├── ConnectorTests.swift │ │ ├── DispatchSourceTests.swift │ │ ├── DistinctTests.swift │ │ ├── DistinctUnionTests.swift │ │ ├── Info.plist │ │ ├── KVOSupportTests.swift │ │ ├── MergedSourceTests.swift │ │ ├── MockArrayObserver.swift │ │ ├── MockSetObserver.swift │ │ ├── MockSink.swift │ │ ├── MockUpdateSink.swift │ │ ├── NSUserDefaultsSupportTests.swift │ │ ├── NotificationCenterSupportTests.swift │ │ ├── ObservableArrayTests.swift │ │ ├── ObservableSetTests.swift │ │ ├── ObservableTypeTests.swift │ │ ├── ObservableValueTests.swift │ │ ├── RefListTests.swift │ │ ├── SetBufferingTests.swift │ │ ├── SetFilteringTests.swift │ │ ├── SetFoldingTests.swift │ │ ├── SetMappingTests.swift │ │ ├── SetReferenceTests.swift │ │ ├── SetSortingTests.swift │ │ ├── SetVariableTests.swift │ │ ├── SignalTests.swift │ │ ├── SimpleSourcesTests.swift │ │ ├── SourceOperatorTests.swift │ │ ├── TestChange.swift │ │ ├── TestObservable.swift │ │ ├── TestUpdatable.swift │ │ ├── TestUtilities.swift │ │ ├── TimerSourceTests.swift │ │ ├── TransactionStateTests.swift │ │ ├── TwoWayBindingTests.swift │ │ ├── TypeHelperTests.swift │ │ ├── UpdatableValueTests.swift │ │ ├── UpdateTests.swift │ │ ├── ValueBufferingTests.swift │ │ ├── ValueChangeTests.swift │ │ ├── ValueMappingTests.swift │ │ ├── ValueReferenceTests.swift │ │ └── VariableTests.swift │ └── PerformanceTests/ │ ├── GlueKitPerformanceTests.swift │ └── Info.plist ├── jazzy.sh └── version.xcconfig ================================================ FILE CONTENTS ================================================ ================================================ FILE: .codecov.yml ================================================ ignore: - "/Tests/*" comment: layout: "header, diff" behavior: default require_changes: no coverage: status: project: default: target: auto threshold: null base: auto paths: "Sources/*" ================================================ FILE: .gitignore ================================================ .build /Carthage/Build /Packages xcuserdata /Package.resolved ================================================ FILE: .gitmodules ================================================ [submodule "Carthage/Checkouts/BTree"] path = Carthage/Checkouts/BTree url = https://github.com/attaswift/BTree.git [submodule "Carthage/Checkouts/SipHash"] path = Carthage/Checkouts/SipHash url = https://github.com/attaswift/SipHash.git ================================================ FILE: .swift-version ================================================ 3.0.1 ================================================ FILE: .travis.yml ================================================ language: objective-c osx_image: xcode9 script: - xcrun xcodebuild -workspace GlueKit.xcworkspace -scheme GlueKit-macOS test - xcrun xcodebuild -quiet -workspace GlueKit.xcworkspace -scheme GlueKit-iOS - xcrun xcodebuild -quiet -workspace GlueKit.xcworkspace -scheme GlueKit-watchOS - xcrun xcodebuild -quiet -workspace GlueKit.xcworkspace -scheme GlueKit-tvOS - swift test after_success: bash <(curl -s https://codecov.io/bash) ================================================ FILE: Cartfile ================================================ github "attaswift/BTree" ~> 4.1 github "attaswift/SipHash" ~> 1.2 ================================================ FILE: Cartfile.resolved ================================================ github "attaswift/BTree" "v4.1.0" github "attaswift/SipHash" "v1.2.0" ================================================ FILE: Demo.playground/Pages/Untitled Page.xcplaygroundpage/Contents.swift ================================================ import GlueKit import XCPlayground // Let's suppose we're writing an app for maintaining a catalogue for your books. // Here is what the model could look like. class Author: Hashable, CustomStringConvertible { let name: Variable let yearOfBirth: Variable init(name: String, yearOfBirth: Int) { self.name = .init(name) self.yearOfBirth = .init(yearOfBirth) } var hashValue: Int { return name.value.hashValue } static func == (a: Author, b: Author) -> Bool { return a.name.value == b.name.value && a.yearOfBirth.value == b.yearOfBirth.value } var description: String { return "Author(\(name.value))" } } class Book: Hashable, CustomStringConvertible { let title: Variable let authors: SetVariable let publicationYear: Variable let pages: Variable init(title: String, authors: Set, publicationYear: Int, pages: Int) { self.title = .init(title) self.authors = .init(authors) self.publicationYear = .init(pages) self.pages = .init(pages) } var hashValue: Int { return title.value.hashValue } static func == (a: Book, b: Book) -> Bool { return (a.title.value == b.title.value && a.authors.value == b.authors.value && a.publicationYear.value == b.publicationYear.value && a.pages.value == b.pages.value) } var description: String { return "Book(\(title.value))" } } class Bookshelf { let location: Variable let books: ArrayVariable init(location: String, books: [Book] = []) { self.location = .init(location) self.books = .init(books) } } // Let's create a couple of example books and arrange them on some bookshelves. let stephenson = Author(name: "Neal Stephenson", yearOfBirth: 1959) let pratchett = Author(name: "Terry Pratchett", yearOfBirth: 1948) let gaiman = Author(name: "Neil Gaiman", yearOfBirth: 1960) let knuth = Author(name: "Donald E. Knuth", yearOfBirth: 1938) let colourOfMagic = Book(title: "The Colour of Magic", authors: [pratchett], publicationYear: 1983, pages: 206) let smallGods = Book(title: "Small Gods", authors: [pratchett], publicationYear: 1992, pages: 284) let seveneves = Book(title: "Seveneves", authors: [stephenson], publicationYear: 2015, pages: 880) let goodOmens = Book(title: "Good Omens", authors: [pratchett, gaiman], publicationYear: 1990, pages: 288) let americanGods = Book(title: "American Gods", authors: [gaiman], publicationYear: 2001, pages: 465) let cryptonomicon = Book(title: "Cryptonomicon", authors: [stephenson], publicationYear: 1999, pages: 918) let anathem = Book(title: "Anathem", authors: [stephenson], publicationYear: 2008, pages: 928) let texBook = Book(title: "The TeXBook", authors: [knuth], publicationYear: 1984, pages: 483) let taocp1 = Book(title: "The Art of Computer Programming vol. 1: Fundamental Algorithms. 3rd ed.", authors: [knuth], publicationYear: 1997, pages: 650) let topShelf = Bookshelf(location: "Top", books: [colourOfMagic, smallGods, seveneves, goodOmens, americanGods]) let bottomShelf = Bookshelf(location: "Bottom", books: [cryptonomicon, anathem, texBook, taocp1]) let shelves = ArrayVariable([topShelf, bottomShelf]) // Now let's create some interesting queries on this small library of books! // Let's get an array of the title of each book in the library. let allTitles = shelves.flatMap{$0.books}.map{$0.title} allTitles.value let allAuthors = shelves.flatMap{$0.books}.distinctUnion().flatMap{$0.authors} allAuthors.value // Here are all books that have Neal Stephenson as one of their authors. let booksByStephenson = shelves.flatMap{$0.books}.filter { book in book.authors.observableContains(stephenson) } booksByStephenson.value // Let's imagine Stephenson was a co-author of The TeXBook, and add him to its author list. texBook.authors.insert(stephenson) // `booksByStephenson` automatically updates to reflect the change. booksByStephenson.value // How many books do I have? let bookCount = shelves.flatMap{$0.books}.observableCount bookCount.value // What if I buy a new book? let mort = Book(title: "Mort", authors: [pratchett], publicationYear: 1987, pages: 315) topShelf.books.append(mort) bookCount allTitles ================================================ FILE: Demo.playground/Pages/Untitled Page.xcplaygroundpage/timeline.xctimeline ================================================ ================================================ FILE: Demo.playground/contents.xcplayground ================================================ ================================================ FILE: Documentation/Language Enhancements.md ================================================ # Potential Swift Improvements That Would Help GlueKit This document lists a couple of potential improvements to the Swift language and compiler that would simplify the design/implementation of GlueKit. **Table of Contents** - [Property Behaviors ([SE-0030])](#property-behaviors-se-0030) - [More Complete Support for Generics](#more-complete-support-for-generics) - [Generalized Protocol Existentials](#generalized-protocol-existentials) - [Permitting `where` clauses to constrain associated types ([SE-0142])](#permitting-where-clauses-to-constrain-associated-types-se-0142) - [Conditional Conformances ([SE-0143])](#conditional-conformances-se-0143) - [Nested generics](#nested-generics) - [Allowing subclasses to override requirements satisfied by defaults](#allowing-subclasses-to-override-requirements-satisfied-by-defaults) - [Abstract Methods](#abstract-methods) - [Relaxed Superclass Visibility Requirements](#relaxed-superclass-visibility-requirements) - [Closure Contexts With Inline Storage (Related issue: [SR-3106])](#closure-contexts-with-inline-storage-related-issue-sr-3106) - [Assorted Bugfixes](#assorted-bugfixes) - [Generic subclass cannot override methos in non-generic superclass, and vice versa ([SR-2427])](#generic-subclass-cannot-override-methos-in-non-generic-superclass-and-vice-versa-sr-2427) ## Property Behaviors ([SE-0030]) > There are property implementation patterns that come up repeatedly. Rather than hardcode a fixed set of patterns into the compiler, we should provide a general "property behavior" mechanism to allow these patterns to be defined as libraries. -- [SE-0030] To declare observable properties in GlueKit, you currently need to declare them with a `Variable` type, and you have to access their values through an inconvenient `value` property: ```swift class Book { let title: Variable let pageCount: Variable } let book: Book = ... book.title.value = "The Colour of Magic" let c = book.pageCount.futureValues.subscribe { print("Page count is now \($0)" } ``` It is possible to make this a little less painful by judicious use of computed properties: ```swift class Book { let observableTitle: Variable var title: String { get { return observableTitle.value } set { observableTitle.value = newValue } } let observablePageCount: Variable var pageCount: Int { get { return observablePageCount.value } set { observablePageCount.value = newValue } } } let book: Book = ... book.title = "The Colour of Magic" let c = book.observablePageCount.futureValues.subscribe { print("Page count is now \($0)" } ``` This makes usage of these properties nicer, but it adds extra boilerplate to the definition of your model classes. If the [proposal for Property Behaviors][SE-0030] would be implemented, we could eliminate this boilerplate and have observable properties that approach (or arguably even exceed) the readability of Cocoa: ```swift class Book { var [observable] title: String var [observable] pageCount: Int } let book: Book = ... book.title = "The Colour of Magic" let c = book.pageCount.[observable].subscribe { print("Page count is now \($0)" } ``` Property Behaviors also allow storage for the Signals associated with these variables to be embedded directly in the `Book` class, instead of being allocated separately. (Each such observable variable needs to count open transactions and keep track of subscribed observers somehow.) [SE-0030]: https://github.com/apple/swift-evolution/blob/master/proposals/0030-property-behavior-decls.md ## More Complete Support for Generics GlueKit heavily relies on Swift's generic types and protocols with associated values. Any and all language improvements relating to these concepts would find immediate use in GlueKit. For example, the implementation of pretty much any item in the [Generics Manifesto][gm] would have immediate impact on GlueKit. (Some of these are explicitly mentioned below.) [gm]: https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md GlueKit was designed with the assumption that most items in the Generic Manifesto will be implemented at some point. I sometimes chose a less convenient way to express things so that when/if a particular item on this list gets implemented, GlueKit will find itself immediately at home in the new language, with minimal design changes. ### Generalized Protocol Existentials > The restrictions on existential types came from an implementation limitation, but it is reasonable to allow a value of protocol type even when the protocol has Self constraints or associated types. -- [GenericsManifesto.md][gm-generalized-existentials] [gm-generalized-existentials]: https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#generalized-existentials GlueKit is largely about the exploration of possible interactions between a handful of concepts, all which are captured by protocols with associated types: `SourceType`, `SinkType`, `ObservableType`, `UpdatableType`, `ObservableValueType`, `ObservableArrayType`, `UpdatableSetType`, etc. The language does not provide existentials for these protocols -- so type erasure needs to be implemented manually. This leads to the proliferation of the various `Any*` types -- see `AnySource`, `AnySink`, `AnyObservableValue`, `AnyUpdatableValue`, `AnyObservableArray`, `AnyUpdatableSetType`, etc. These are all implemented manually, using subclass polymorphism. Sometimes the need for type erasure can be eliminated by expanding the public API surface. For example, wherever an `Any*` type is used as a return value, we could substitute the (currently private/internal) concrete type instead. There are good reasons to do this whether or not we get generalized existentials: having transformation methods return concrete types enables static method dispatch. It might be worthwhile to do this for at least some of the transformations. (The class names and their exposed API surface need to be carefully reviewed and updated, though.) Concrete return types do make life a little more difficult for users, though: they will have to explicitly specify the type in, say, a property declaration. However, the need for `Any*` cannot be entirely eliminated. For example, `Signal` needs to be able to store its subscribers in a collection, which is only possible with some form of type erasure. (The current `Source` API also needs `Signal` to be able to reify type-erased values, getting the original type back. This is similar to [opening a generalized existential][gm-open].) Doing type erasure by subclass polymorphism implies that GlueKit's `Any*` types always need to heap-allocate a box. On the other hand, Swift's protocol existentials include some space to store small value types inline, which makes them work without allocation in a lot of cases. This optimization is hard (even impossible?) to emulate in Swift code, but it would be very much desirable to have it for e.g. `AnySink`. [gm-open]: https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#opening-existentials ### Permitting `where` clauses to constrain associated types ([SE-0142]) > This proposal seeks to introduce a where clause to associated type declarations and improvements to protocol constraints to bring associated types the same expressive power as generic type parameters. -- [SE-0142] [SE-0142]: https://github.com/apple/swift-evolution/blob/master/proposals/0142-associated-types-constraints.md This is a biggie! Not having the ability to specify that e.g. an `ObservableArrayType` must have `Change == ArrayChange` means that this constraint has to be explicitly specified by almost every consumer of an observable array. I currently have ~300 such explicit constraints in the source code, making working on GlueKit an exercise in patience. The proposal to implement this improvement has been accepted, so presumably it'll get implemented relatively soon. Meanwhile, we can mostly work around the issue by having the child protocol include property requirements that specialize on the requirements of the parent: ```swift protocol ObservableType { // Not the real definition associatedtype Change: ChangeType var value: Change.Value { get } var changes: AnySource { get } } protocol ObservableArrayType { associatedtype Element: Hashable var value: [Element] { get } var changes: AnySource> { get } } ``` In most (maybe all) contexts, the type inference engine sees that `changes` in `ObservableArrayType` refines the similar requirement in `ObservableType` and concludes that `Change` can only be an `ArrayChange` there. In the current codebase, `ObservableType` doesn't have a property requirement in like `changes` above -- the update source is implemented by two methods. However, it would be easy enough to define a dummy property that is never actually used. ### Conditional Conformances ([SE-0143]) > Conditional conformances express the notion that a generic type will conform to a particular protocol only when its type arguments meet certain requirements." -- [SE-0143] This is a popular Swift feature request that seems to be on track for implementation. [SE-0143]: https://github.com/apple/swift-evolution/blob/master/proposals/0143-conditional-conformances.md At first glance, it looks like conditional conformances might allow us to express deep relationships between our concepts: for example, we might want to say that an `ObservableType` whose `Change` is an `ArrayChange` is automatically an `ObservableArrayType`. But that's not the case: 1. The proposal allows conditional conformances for generic types only, not protocols. It enables us to specify that `Array` should implement `Equatable` when its elements do, but we won't be able to do the same for `Collection`. 2. Even if we could do that, an `ObservableArrayType` is more than just an observable whose delta type matches a specific structure; it also defines a more efficient API for array access. Without this, we lose what makes observable arrays (reasonably) efficient. Conditional conformances will, however, help a great deal with small annoying details -- for example, we could say that a `Variable` with an integer value automatically implements `ExpressibleByIntegerLiteral`, eliminating the need for `IntVariable`. We could have `ValueChange`, `SetChange` and `ArrayChange` explicitly implement `Equatable`, which would help unit tests. ### Nested generics > There isn't much to say about this: the compiler simply needs to be improved to handle nested generics throughout. -- [GenericsManifesto.md][gm-nested-generics] [gm-nested-generics]: https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#nested-generics Currently Swift does not support declaring nested types in generics, which means some helper types (like method forwarding structs that implement `SinkType`) need to be declared outside the generic struct/class. This means the type parameters of the parent generic and (most annoyingly) the constraints of these parameters need to be repeated on every such helper type. Assuming types nested in generics would implicitly inherit the type parameters of their parents, nested generics would eliminate such boring repetitions. ### Allowing subclasses to override requirements satisfied by defaults > When a superclass conforms to a protocol and has one of the protocol's requirements satisfied by a member of a protocol extension, that member currently cannot be overridden by a subclass. -- [GenericsManifesto.md][gm-overriding-extensions] [gm-overriding-extensions]: https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#allowing-subclasses-to-override-requirements-satisfied-by-defaults- For each protocol requirement that has a default implementation in a protocol extension, we currently need to duplicate that implementation in the (abstract) base class that is used to implement boxing for type erasure. This is error-prone and ugly. ## Abstract Methods There is currently no way to mark an overridable member as abstract in Swift. I've not seen proposals to add support for this, but it seems obvious Swift needs this -- abstract classes and members are an important part of OOP. Without the help of the compiler, it is all too easy to forget a required method override. Protocols supply an alternative, often better, way to express the things we used to model with abstract methods. But in some contexts (particularly when implementing type-erasure for protocols with associated types), abstract methods are still useful. (Obviously, generalized existentials would remove this usecase.) ## Relaxed Superclass Visibility Requirements Swift currently requires the superclasses of public classes to be also exposed as public, even if they're just an implementation detail. This is why GlueKit exposes some underscored classes like `_BaseObservableArray` in its public interface. This restriction is not consistently enforced by the compiler, but breaking it can lead to misleading errors and/or strange behavior at runtime. (I can't remember which.) ## Closure Contexts With Inline Storage (Related issue: [SR-3106]) [SR-3106]: https://bugs.swift.org/browse/SR-3106 In the current version of Swift, a variable of a function type has storage for just two machine words, which is enough to hold function pointer and a single-word context containing the values captured by the closure. This means that the context of most escaping closures needs to be heap-allocated, even if they capture only a couple of constants. The compiler may be able to optimize away the allocation if the closure is non-escaping, but if we need to store the closure for later execution, the context needs to be on the heap. Closures that capture only a single-word constant may at some point store that value directly in the context word. This isn't currently implemented, though. (Note that the captured value may or may not need to be reference counted. `self` is refcounted, but a captured `Float` wouldn't be. Some bits need to be reserved somewhere to keep track of this.) Swift's partially applied methods are closures that capture `self`. It would be possible to special-case them such that the `self` reference is stored directly in context word; but this is not currently implemented. So partially applied methods always incur the cost of a heap allocation when used in escaping contexts. This makes them slower than representing the method call with a forwarding struct like this: ```swift class Foo { func greet(_ name: String) { print("Hello, \(name)!") } } struct FooForwarder { let foo: Foo func apply(_ name: String) { foo.greet(name) } } let foo = Foo() let greeter1 = FooForwarder(foo) let greeter2 = foo.greet ``` Creating `greeter1` is measurably faster than creating `greeter2`, because the latter needs to do an allocation. This is the primary reason why GlueKit defines the `SinkType` protocol instead of just defining a typealias for a function type. (The other reason is that it is useful to be able to add extra requirements (i.e., `Hashable`) and extensions to sinks. But we could live without those!) If the size of function types would be widened to include space for more bits of context, lots of simple closures would fit inline, without the need for context allocation. There is precedent for this with `Any`, which currently includes space for inline storage of data up to three words in size. Aggregate observation in GlueKit (e.g., `ArrayMappingForValueField`) needs to register observer sinks that capture two refcounted pointers -- one for the `self` pointer of the aggregate observable itself, the other for keeping track of the particular element that is the source of the change. If these would be represented by closures, then they would be heap-allocated, making them much more expensive -- unless Swift's function types would be widened & the inline storage optimization was implemented. (We could leave some of the captures uncaptured and store them alongside the closure, but it seems easier to just define a forwarding struct -- which achieves exactly the same thing.) ## Assorted Bugfixes This section lists compiler bugs that have a direct effect on GlueKit. ### Generic subclass cannot override methos in non-generic superclass, and vice versa ([SR-2427]) This compiler bug affects the design of `TimerSource`. To work around it, I had to add a dummy unusued type parameter to it, and define a typealias to paper over the ugliness. [SR-2427]: https://bugs.swift.org/browse/SR-2427 This bug [has been fixed][PR-5424] on Swift's master branch. [PR-5424]: https://github.com/apple/swift/pull/5424 ================================================ FILE: Documentation/Overview.md ================================================ # Overview of GlueKit **Table of Contents** - [Sources, Sinks and Connections](#sources-sinks-and-connections) - [Signals](#signals) - [Observables, Updatables and Variable](#observables-updatables-and-variable) ## Sources, Sinks and Connections In GlueKit, the `SourceType` protocol models a thing that you can subscribe to receive a stream of values. ```Swift protocol SourceType { typealias Value func subscribe(sink: Value->Void) -> Connection } ``` It is not easy to work with associated types, so GlueKit generally uses the struct `Source` to represent sources. `Source` is a type-erased wrapper around some `SourceType` for the same type of value. For example, you can create a source for the `NSCalendarDayChangedNotification` notification and run some code whenever midnight passes: ```Swift let center = NSNotificationCenter.defaultCenter() let midnightSource = center.source(forName: NSCalendarDayChangedNotification) let connection = midnightSource.subscribe { notification in print("Ding dong!") } ``` The subscription (and the source) is kept alive until the connection object is deinitalized or until its `disconnect` method is called. Thus, active connections hold strong references to their source and sink---this is a general rule in GlueKit. There is a `Connector` class that is a convenient place to store connections that you need to keep alive without creating a property for each individual connection: ```Swift class ClockViewController: UIViewController { private connectionsWhileVisible = Connector() // TimerSource is a source that periodically broadcasts a Void value as long as it is connected. private let tickSource = TimerSource(start: NSDate(), interval: 1.0) private var midnightSource: Source { let center = NSNotificationCenter.defaultCenter() return center.source(forName: NSCalendarDayChangedNotification) } override func viewWillAppear() { super.viewWillAppear() tickSource.subscribe(self.onTick).putInto(connectionsWhileVisible) midnightSource.subscribe { _ in self.onMidnight() }.putInto(connectionsWhileVisible) } override func viewDidDisappear() { super.viewDidDisappear() connectionsWhileVisible.disconnect() } func onTick() { print("Tick!") } func onMidnight() { print("Ding dong!") } } ``` ## Signals The `Signal` class lets you easily create your own sources. It implements `SourceType`, keeps track of its connections, and provides a `send` method for you to send values to all its connected sinks: ```Swift let signal = Signal() signal.send(42) // Does nothing, no subscribers let connection = signal.subscribe { i in print("Got \(i\)!") } signal.send(23) // Prints "Got 23!" signal.send(7) // Prints "Got 7!" connection.disconnect() ``` For example, a button's implementation might have a Void-valued signal that triggers whenever the user taps on it. In such cases, it's best to not let outside code send values to the signal, so the `Signal` instance is frequently stored in a private property and the class exposes only a "read-only" source view of it: ```Swift class Button: UIButton { private let activationSignal: Signal() public var activationSource: Source { return activationSignal.source } init() { super.init(...) addTarget(self, action: "didTouchUpInside", forControlEvents: UIControlEventTouchUpInside) } @objc func didTouchUpInside() { activationSignal.send() } } ``` This is the way most sources are implemented in GlueKit. ## Observables, Updatables and Variable A concrete implementation of this protocol is the generic class `Variable`, which directly holds such a value. Let's create a class with an observable property. ```Swift class Person { let name: Variable init(name: String) { self.name.value = name } } let fred = Person(name: "Fred") // The variable's value property let's you get Fred's name or update it: print(fred.name.value) fred.name.value = "Freddie" ``` `let foo: Variable` is the GlueKit equivalent to Cocoa's `dynamic var foo: Type`. Once you have an observable value, you can start observing it: ```Swift let connection = fred.name.subscribe { name in print("Fred's name is \(name)") } ``` This will immediately print `Fred's name is Fred` to the console, because by default variables send their current values to each new observer. If you don't want this, you can subscribe to the variable's `futureValues` property instead. ================================================ FILE: GlueKit.podspec ================================================ Pod::Spec.new do |spec| spec.name = 'GlueKit' spec.version = '0.2.0' spec.ios.deployment_target = "9.3" spec.osx.deployment_target = "10.11" spec.tvos.deployment_target = "10.0" spec.watchos.deployment_target = "3.0" spec.summary = 'Type-safe observable values and collections in Swift' spec.author = 'Károly Lőrentey' spec.homepage = 'https://github.com/attaswift/GlueKit' spec.license = { :type => 'MIT', :file => 'LICENSE.md' } spec.source = { :git => 'https://github.com/attaswift/GlueKit.git', :tag => 'v' + String(spec.version) } spec.source_files = 'Sources/*.swift' spec.social_media_url = 'https://twitter.com/lorentey' #spec.documentation_url = 'http://lorentey.github.io/GlueKit/' spec.dependency 'BTree', '~> 4.1' spec.dependency 'SipHash', '~> 1.2' end ================================================ FILE: GlueKit.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ BB015F2E1DC4F5BA00C8C05A /* ObservableSetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E411DABD4BE00E85FB9 /* ObservableSetTests.swift */; }; BB015F2F1DC4F5C200C8C05A /* UpdatableArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B341C8FCC820050DDA9 /* UpdatableArray.swift */; }; BB015F301DC4F64200C8C05A /* UpdatableSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB89901A1D5F6AAE0097F350 /* UpdatableSet.swift */; }; BB015F311DC4F90200C8C05A /* MockSetObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB568AF21DA6942700BC4B58 /* MockSetObserver.swift */; }; BB015F321DC4FC3100C8C05A /* SetVariable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB89901F1D5F6F980097F350 /* SetVariable.swift */; }; BB015F331DC4FC3700C8C05A /* ArrayVariable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B201C8FCC810050DDA9 /* ArrayVariable.swift */; }; BB015F341DC4FDCE00C8C05A /* SetMappingBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE4DF5B1DA55DA3005EC162 /* SetMappingBase.swift */; }; BB015F351DC4FDD300C8C05A /* ObservableContains.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBF400631DA108E900DA0B2C /* ObservableContains.swift */; }; BB015F361DC5004F00C8C05A /* ValueMappingForSetField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E1D1DAAC7E600E85FB9 /* ValueMappingForSetField.swift */; }; BB015F371DC506B400C8C05A /* ValueMappingForArrayField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E131DAAC4DC00E85FB9 /* ValueMappingForArrayField.swift */; }; BB015F381DC508AC00C8C05A /* BufferedArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB69A7FF1D6B2377001D2821 /* BufferedArray.swift */; }; BB015F391DC509D000C8C05A /* SetMappingForValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE4DF561DA55384005EC162 /* SetMappingForValue.swift */; }; BB015F3A1DC50B1200C8C05A /* ArrayMappingForValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB69A83F1D6B9A7E001D2821 /* ArrayMappingForValue.swift */; }; BB015F3B1DC50C6700C8C05A /* MockArrayObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB568AF61DA6A9EE00BC4B58 /* MockArrayObserver.swift */; }; BB015F3C1DC5106B00C8C05A /* ArrayMappingForValueField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B2D1C8FCC810050DDA9 /* ArrayMappingForValueField.swift */; }; BB015F3D1DC512D600C8C05A /* ArrayMappingForArrayField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7E04C71DA7233B00BE3051 /* ArrayMappingForArrayField.swift */; }; BB015F3E1DC5147700C8C05A /* ArrayMappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B9F1C8FCE2C0050DDA9 /* ArrayMappingTests.swift */; }; BB015F3F1DC51D8300C8C05A /* ArrayVariableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B981C8FCE2C0050DDA9 /* ArrayVariableTests.swift */; }; BB015F401DC5261900C8C05A /* ArrayFilteringIndexmap.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7E04E01DA7255D00BE3051 /* ArrayFilteringIndexmap.swift */; }; BB015F411DC5262400C8C05A /* ArrayFilteringOnPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB82E3E01D98F34E0035DDAD /* ArrayFilteringOnPredicate.swift */; }; BB015F421DC526BF00C8C05A /* ArrayFilteringOnObservableBool.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7E04E51DA725A500BE3051 /* ArrayFilteringOnObservableBool.swift */; }; BB015F431DC5281F00C8C05A /* ArrayReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3D929A1D64862D003BEDBF /* ArrayReference.swift */; }; BB015F441DC529CF00C8C05A /* ArrayFilteringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBC4AE4D1D9A829500FF7DE0 /* ArrayFilteringTests.swift */; }; BB015F451DC52ADD00C8C05A /* ArrayFolding.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9DF81DAA885E00E85FB9 /* ArrayFolding.swift */; }; BB015F461DC52B4500C8C05A /* ArrayFoldingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9DFD1DAA8F7800E85FB9 /* ArrayFoldingTests.swift */; }; BB015F471DC52B6200C8C05A /* ArrayConcatenation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB69A83A1D6B90F5001D2821 /* ArrayConcatenation.swift */; }; BB015F481DC52CE200C8C05A /* ArrayConcatenationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E491DABFA2900E85FB9 /* ArrayConcatenationTests.swift */; }; BB015F491DC52D3500C8C05A /* DistinctUnion.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE4DF491DA409EE005EC162 /* DistinctUnion.swift */; }; BB015F4A1DC52DC500C8C05A /* DistinctUnionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE4DF4E1DA412A0005EC162 /* DistinctUnionTests.swift */; }; BB015F4B1DC52FA000C8C05A /* ObservableArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E451DABEA5500E85FB9 /* ObservableArrayTests.swift */; }; BB015F4C1DC6233E00C8C05A /* SetMappingForSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7E04D11DA7240C00BE3051 /* SetMappingForSequence.swift */; }; BB015F4F1DC677A900C8C05A /* SetMappingForValueField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7E04CC1DA723F300BE3051 /* SetMappingForValueField.swift */; }; BB015F501DC678C200C8C05A /* SetMappingForSetField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7E04D61DA7242F00BE3051 /* SetMappingForSetField.swift */; }; BB015F511DC679F000C8C05A /* SetMappingForArrayField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7E04DB1DA7244A00BE3051 /* SetMappingForArrayField.swift */; }; BB015F531DC67A6400C8C05A /* SetVariableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E4D1DABFE8800E85FB9 /* SetVariableTests.swift */; }; BB015F541DC67ACA00C8C05A /* SetMappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE4DF611DA5654F005EC162 /* SetMappingTests.swift */; }; BB015F551DC67C1000C8C05A /* SetFilteringOnPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B231C8FCC810050DDA9 /* SetFilteringOnPredicate.swift */; }; BB015F561DC67C3F00C8C05A /* SetReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3D92951D647E83003BEDBF /* SetReference.swift */; }; BB015F571DC67DA900C8C05A /* SetFilteringOnObservableBool.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBA033561DA726AA00E83AC1 /* SetFilteringOnObservableBool.swift */; }; BB015F581DC67F0500C8C05A /* SetFilteringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB568AEE1DA693F700BC4B58 /* SetFilteringTests.swift */; }; BB015F591DC684DC00C8C05A /* SetFoldingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E061DAA918400E85FB9 /* SetFoldingTests.swift */; }; BB015F5A1DC684E200C8C05A /* SetFolding.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E011DAA910500E85FB9 /* SetFolding.swift */; }; BB015F5C1DC6868F00C8C05A /* SetSortingByMappingToComparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1E4D691D61EA5200F1831F /* SetSortingByMappingToComparable.swift */; }; BB015F5D1DC686CD00C8C05A /* SetSortingByMappingToObservableComparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBA033601DA7278C00E83AC1 /* SetSortingByMappingToObservableComparable.swift */; }; BB015F5E1DC687B100C8C05A /* SetSortingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB471D991DA57FF8002550B0 /* SetSortingTests.swift */; }; BB015F5F1DC687EE00C8C05A /* SetSortingByComparator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBA0335B1DA7276200E83AC1 /* SetSortingByComparator.swift */; }; BB015F601DC689F600C8C05A /* Bookshelf.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB8D366D1DA116CB000D44C5 /* Bookshelf.swift */; }; BB1657E71D71D65900094BAC /* ArrayChangeSeparation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1657E61D71D65900094BAC /* ArrayChangeSeparation.swift */; }; BB1657E91D71D65900094BAC /* ArrayChangeSeparation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1657E61D71D65900094BAC /* ArrayChangeSeparation.swift */; }; BB1657EA1D71D65900094BAC /* ArrayChangeSeparation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1657E61D71D65900094BAC /* ArrayChangeSeparation.swift */; }; BB170F241DC1066D0000443E /* UpdateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB170F231DC1066D0000443E /* UpdateTests.swift */; }; BB170F251DC1066D0000443E /* UpdateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB170F231DC1066D0000443E /* UpdateTests.swift */; }; BB170F261DC1066D0000443E /* UpdateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB170F231DC1066D0000443E /* UpdateTests.swift */; }; BB170F281DC106DE0000443E /* TestChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB170F271DC106DE0000443E /* TestChange.swift */; }; BB170F291DC106DE0000443E /* TestChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB170F271DC106DE0000443E /* TestChange.swift */; }; BB170F2A1DC106DE0000443E /* TestChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB170F271DC106DE0000443E /* TestChange.swift */; }; BB170F2C1DC10D350000443E /* TestObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB170F2B1DC10D350000443E /* TestObservable.swift */; }; BB170F2D1DC10D350000443E /* TestObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB170F2B1DC10D350000443E /* TestObservable.swift */; }; BB170F2E1DC10D350000443E /* TestObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB170F2B1DC10D350000443E /* TestObservable.swift */; }; BB170F301DC10DDB0000443E /* ChangesSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB170F2F1DC10DDB0000443E /* ChangesSourceTests.swift */; }; BB170F311DC10DDB0000443E /* ChangesSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB170F2F1DC10DDB0000443E /* ChangesSourceTests.swift */; }; BB170F321DC10DDB0000443E /* ChangesSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB170F2F1DC10DDB0000443E /* ChangesSourceTests.swift */; }; BB170F341DC110BD0000443E /* TestUpdatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB170F331DC110BD0000443E /* TestUpdatable.swift */; }; BB170F351DC110BD0000443E /* TestUpdatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB170F331DC110BD0000443E /* TestUpdatable.swift */; }; BB170F361DC110BD0000443E /* TestUpdatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB170F331DC110BD0000443E /* TestUpdatable.swift */; }; BB170F381DC1545E0000443E /* TransactionStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB170F371DC1545E0000443E /* TransactionStateTests.swift */; }; BB170F391DC1545E0000443E /* TransactionStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB170F371DC1545E0000443E /* TransactionStateTests.swift */; }; BB170F3A1DC1545E0000443E /* TransactionStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB170F371DC1545E0000443E /* TransactionStateTests.swift */; }; BB170F3B1DC220A80000443E /* MockUpdateSink.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E0A1DAAB6E700E85FB9 /* MockUpdateSink.swift */; }; BB170F3C1DC220B20000443E /* ObservableValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B9D1C8FCE2C0050DDA9 /* ObservableValueTests.swift */; }; BB170F3E1DC221180000443E /* ValueChangeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB170F3D1DC221180000443E /* ValueChangeTests.swift */; }; BB170F3F1DC221180000443E /* ValueChangeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB170F3D1DC221180000443E /* ValueChangeTests.swift */; }; BB170F401DC221180000443E /* ValueChangeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB170F3D1DC221180000443E /* ValueChangeTests.swift */; }; BB170F421DC22A300000443E /* UpdatableValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB170F411DC22A300000443E /* UpdatableValueTests.swift */; }; BB170F431DC22A300000443E /* UpdatableValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB170F411DC22A300000443E /* UpdatableValueTests.swift */; }; BB170F441DC22A300000443E /* UpdatableValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB170F411DC22A300000443E /* UpdatableValueTests.swift */; }; BB170F451DC239BC0000443E /* DistinctTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B9E1C8FCE2C0050DDA9 /* DistinctTests.swift */; }; BB170F461DC255200000443E /* TwoWayBindingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55BA61C8FCE2C0050DDA9 /* TwoWayBindingTests.swift */; }; BB23E63D1C90DEB6005EFD0A /* GlueKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BBB55AB21C8F80020050DDA9 /* GlueKit.framework */; }; BB318F6B1CB992300086EE83 /* UIBarButtonItem Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB318F6A1CB9922F0086EE83 /* UIBarButtonItem Extensions.swift */; }; BB351B011DB81E67005F083F /* GlueKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BBB55AD41C8F88F20050DDA9 /* GlueKit.framework */; }; BB351B071DB81EE1005F083F /* GlueKitPerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55BEA1C8FD25F0050DDA9 /* GlueKitPerformanceTests.swift */; }; BB3982ED1EC109C4000CDCB5 /* UISearchBar Glue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3982EC1EC109C4000CDCB5 /* UISearchBar Glue.swift */; }; BB3982EE1EC109C7000CDCB5 /* UISearchBar Glue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3982EC1EC109C4000CDCB5 /* UISearchBar Glue.swift */; }; BB3982EF1EC109C8000CDCB5 /* UISearchBar Glue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3982EC1EC109C4000CDCB5 /* UISearchBar Glue.swift */; }; BB3982F01EC109C8000CDCB5 /* UISearchBar Glue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3982EC1EC109C4000CDCB5 /* UISearchBar Glue.swift */; }; BB3D12B21E49FC5A00097510 /* BTree.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB3D12B11E49FC5A00097510 /* BTree.framework */; }; BB3D12B41E49FC5E00097510 /* SipHash.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB3D12B31E49FC5E00097510 /* SipHash.framework */; }; BB3D12B71E49FC7600097510 /* BTree.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB3D12B51E49FC7600097510 /* BTree.framework */; }; BB3D12B81E49FC7600097510 /* SipHash.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB3D12B61E49FC7600097510 /* SipHash.framework */; }; BB3D12BB1E49FC8000097510 /* BTree.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB3D12B91E49FC8000097510 /* BTree.framework */; }; BB3D12BC1E49FC8000097510 /* SipHash.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB3D12BA1E49FC8000097510 /* SipHash.framework */; }; BB3D12BF1E49FC8700097510 /* BTree.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB3D12BD1E49FC8700097510 /* BTree.framework */; }; BB3D12C01E49FC8700097510 /* SipHash.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB3D12BE1E49FC8700097510 /* SipHash.framework */; }; BB3D12C11E49FD1700097510 /* GlueKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BBB55AD41C8F88F20050DDA9 /* GlueKit.framework */; }; BB4052021C958712003D8F5B /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB4052011C958712003D8F5B /* UIKit.framework */; }; BB471D9A1DA57FF8002550B0 /* SetSortingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB471D991DA57FF8002550B0 /* SetSortingTests.swift */; }; BB471D9C1DA57FF8002550B0 /* SetSortingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB471D991DA57FF8002550B0 /* SetSortingTests.swift */; }; BB568AEF1DA693F700BC4B58 /* SetFilteringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB568AEE1DA693F700BC4B58 /* SetFilteringTests.swift */; }; BB568AF11DA693F700BC4B58 /* SetFilteringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB568AEE1DA693F700BC4B58 /* SetFilteringTests.swift */; }; BB568AF31DA6942700BC4B58 /* MockSetObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB568AF21DA6942700BC4B58 /* MockSetObserver.swift */; }; BB568AF51DA6942700BC4B58 /* MockSetObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB568AF21DA6942700BC4B58 /* MockSetObserver.swift */; }; BB568AF91DA6A9EE00BC4B58 /* MockArrayObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB568AF61DA6A9EE00BC4B58 /* MockArrayObserver.swift */; }; BB5B02DB1F5E3FC50084D86B /* ArrayGatheringSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB5B02DA1F5E3FC50084D86B /* ArrayGatheringSource.swift */; }; BB5B02DC1F5E3FC50084D86B /* ArrayGatheringSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB5B02DA1F5E3FC50084D86B /* ArrayGatheringSource.swift */; }; BB5B02DD1F5E3FC50084D86B /* ArrayGatheringSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB5B02DA1F5E3FC50084D86B /* ArrayGatheringSource.swift */; }; BB5B02DE1F5E3FC50084D86B /* ArrayGatheringSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB5B02DA1F5E3FC50084D86B /* ArrayGatheringSource.swift */; }; BB5B02E01F5E3FDE0084D86B /* SetGatheringSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB5B02DF1F5E3FDE0084D86B /* SetGatheringSource.swift */; }; BB5B02E11F5E3FDE0084D86B /* SetGatheringSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB5B02DF1F5E3FDE0084D86B /* SetGatheringSource.swift */; }; BB5B02E21F5E3FDE0084D86B /* SetGatheringSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB5B02DF1F5E3FDE0084D86B /* SetGatheringSource.swift */; }; BB5B02E31F5E3FDE0084D86B /* SetGatheringSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB5B02DF1F5E3FDE0084D86B /* SetGatheringSource.swift */; }; BB6139831F5E1B3F005455D5 /* NSTextField Glue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB6139821F5E1B3F005455D5 /* NSTextField Glue.swift */; }; BB67E5521C9B6A63002B0AB5 /* CADisplayLink Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB67E5511C9B6A63002B0AB5 /* CADisplayLink Extensions.swift */; }; BB67E5531C9B6A63002B0AB5 /* CADisplayLink Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB67E5511C9B6A63002B0AB5 /* CADisplayLink Extensions.swift */; }; BB69A7EF1D6AFF1A001D2821 /* Abstract.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB69A7EE1D6AFF1A001D2821 /* Abstract.swift */; }; BB69A7F01D6AFF1A001D2821 /* Abstract.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB69A7EE1D6AFF1A001D2821 /* Abstract.swift */; }; BB69A7F11D6AFF1A001D2821 /* Abstract.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB69A7EE1D6AFF1A001D2821 /* Abstract.swift */; }; BB69A7F21D6AFF1A001D2821 /* Abstract.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB69A7EE1D6AFF1A001D2821 /* Abstract.swift */; }; BB69A7F31D6B1D7B001D2821 /* Change.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB8990041D5DF8630097F350 /* Change.swift */; }; BB69A7F51D6B1D7C001D2821 /* Change.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB8990041D5DF8630097F350 /* Change.swift */; }; BB69A7F61D6B1D7C001D2821 /* Change.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB8990041D5DF8630097F350 /* Change.swift */; }; BB69A7F71D6B1D82001D2821 /* ArrayChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B1F1C8FCC810050DDA9 /* ArrayChange.swift */; }; BB69A7F91D6B1D83001D2821 /* ArrayChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B1F1C8FCC810050DDA9 /* ArrayChange.swift */; }; BB69A7FA1D6B1D83001D2821 /* ArrayChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B1F1C8FCC810050DDA9 /* ArrayChange.swift */; }; BB69A7FB1D6B1D94001D2821 /* ObservableArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B2A1C8FCC810050DDA9 /* ObservableArray.swift */; }; BB69A7FD1D6B1D95001D2821 /* ObservableArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B2A1C8FCC810050DDA9 /* ObservableArray.swift */; }; BB69A7FE1D6B1D95001D2821 /* ObservableArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B2A1C8FCC810050DDA9 /* ObservableArray.swift */; }; BB69A8001D6B2377001D2821 /* BufferedArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB69A7FF1D6B2377001D2821 /* BufferedArray.swift */; }; BB69A8021D6B2377001D2821 /* BufferedArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB69A7FF1D6B2377001D2821 /* BufferedArray.swift */; }; BB69A8031D6B2377001D2821 /* BufferedArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB69A7FF1D6B2377001D2821 /* BufferedArray.swift */; }; BB69A8041D6B344C001D2821 /* UpdatableArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B341C8FCC820050DDA9 /* UpdatableArray.swift */; }; BB69A8061D6B344D001D2821 /* UpdatableArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B341C8FCC820050DDA9 /* UpdatableArray.swift */; }; BB69A8071D6B344E001D2821 /* UpdatableArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B341C8FCC820050DDA9 /* UpdatableArray.swift */; }; BB69A8081D6B442B001D2821 /* ArrayVariable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B201C8FCC810050DDA9 /* ArrayVariable.swift */; }; BB69A80A1D6B442C001D2821 /* ArrayVariable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B201C8FCC810050DDA9 /* ArrayVariable.swift */; }; BB69A80B1D6B442D001D2821 /* ArrayVariable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B201C8FCC810050DDA9 /* ArrayVariable.swift */; }; BB69A80C1D6B472D001D2821 /* ArrayMappingForValueField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B2D1C8FCC810050DDA9 /* ArrayMappingForValueField.swift */; }; BB69A80E1D6B472F001D2821 /* ArrayMappingForValueField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B2D1C8FCC810050DDA9 /* ArrayMappingForValueField.swift */; }; BB69A80F1D6B472F001D2821 /* ArrayMappingForValueField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B2D1C8FCC810050DDA9 /* ArrayMappingForValueField.swift */; }; BB69A8101D6B5397001D2821 /* ValueMappingForValueField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B2C1C8FCC810050DDA9 /* ValueMappingForValueField.swift */; }; BB69A8131D6B539A001D2821 /* ValueMappingForValueField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B2C1C8FCC810050DDA9 /* ValueMappingForValueField.swift */; }; BB69A8141D6B539A001D2821 /* ValueMappingForValueField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B2C1C8FCC810050DDA9 /* ValueMappingForValueField.swift */; }; BB69A8191D6B6A99001D2821 /* ArrayReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3D929A1D64862D003BEDBF /* ArrayReference.swift */; }; BB69A81B1D6B6A9A001D2821 /* ArrayReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3D929A1D64862D003BEDBF /* ArrayReference.swift */; }; BB69A81C1D6B6A9B001D2821 /* ArrayReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3D929A1D64862D003BEDBF /* ArrayReference.swift */; }; BB69A81D1D6B71F9001D2821 /* SetChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB8990021D5DF2D20097F350 /* SetChange.swift */; }; BB69A81F1D6B71FA001D2821 /* SetChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB8990021D5DF2D20097F350 /* SetChange.swift */; }; BB69A8201D6B71FB001D2821 /* SetChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB8990021D5DF2D20097F350 /* SetChange.swift */; }; BB69A8231D6B7203001D2821 /* ObservableSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB8990091D5DF88C0097F350 /* ObservableSet.swift */; }; BB69A8241D6B7203001D2821 /* ObservableSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB8990091D5DF88C0097F350 /* ObservableSet.swift */; }; BB69A8281D6B779A001D2821 /* UpdatableSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB89901A1D5F6AAE0097F350 /* UpdatableSet.swift */; }; BB69A8291D6B779B001D2821 /* UpdatableSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB89901A1D5F6AAE0097F350 /* UpdatableSet.swift */; }; BB69A82C1D6B7CAE001D2821 /* SetVariable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB89901F1D5F6F980097F350 /* SetVariable.swift */; }; BB69A82D1D6B7CAF001D2821 /* SetVariable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB89901F1D5F6F980097F350 /* SetVariable.swift */; }; BB69A8301D6B7E36001D2821 /* SetSortingByMappingToComparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1E4D691D61EA5200F1831F /* SetSortingByMappingToComparable.swift */; }; BB69A8311D6B7E37001D2821 /* SetSortingByMappingToComparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1E4D691D61EA5200F1831F /* SetSortingByMappingToComparable.swift */; }; BB69A8341D6B7F12001D2821 /* SetReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3D92951D647E83003BEDBF /* SetReference.swift */; }; BB69A8351D6B7F12001D2821 /* SetReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3D92951D647E83003BEDBF /* SetReference.swift */; }; BB69A8381D6B7F9D001D2821 /* SetFilteringOnPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B231C8FCC810050DDA9 /* SetFilteringOnPredicate.swift */; }; BB69A8391D6B7F9E001D2821 /* SetFilteringOnPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B231C8FCC810050DDA9 /* SetFilteringOnPredicate.swift */; }; BB69A83B1D6B90F5001D2821 /* ArrayConcatenation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB69A83A1D6B90F5001D2821 /* ArrayConcatenation.swift */; }; BB69A83D1D6B90F5001D2821 /* ArrayConcatenation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB69A83A1D6B90F5001D2821 /* ArrayConcatenation.swift */; }; BB69A83E1D6B90F5001D2821 /* ArrayConcatenation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB69A83A1D6B90F5001D2821 /* ArrayConcatenation.swift */; }; BB69A8401D6B9A7E001D2821 /* ArrayMappingForValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB69A83F1D6B9A7E001D2821 /* ArrayMappingForValue.swift */; }; BB69A8421D6B9A7E001D2821 /* ArrayMappingForValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB69A83F1D6B9A7E001D2821 /* ArrayMappingForValue.swift */; }; BB69A8431D6B9A7E001D2821 /* ArrayMappingForValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB69A83F1D6B9A7E001D2821 /* ArrayMappingForValue.swift */; }; BB6CE3B41DC4C21C00295C55 /* TransformedSink.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB6CE3B31DC4C21C00295C55 /* TransformedSink.swift */; }; BB6CE3B51DC4C21C00295C55 /* TransformedSink.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB6CE3B31DC4C21C00295C55 /* TransformedSink.swift */; }; BB6CE3B61DC4C21C00295C55 /* TransformedSink.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB6CE3B31DC4C21C00295C55 /* TransformedSink.swift */; }; BB6CE3B71DC4C21C00295C55 /* TransformedSink.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB6CE3B31DC4C21C00295C55 /* TransformedSink.swift */; }; BB6CE3B81DC4CD3900295C55 /* Change.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB8990041D5DF8630097F350 /* Change.swift */; }; BB6CE3B91DC4CD3B00295C55 /* Update.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE9AAC91DC0F0A5000B3DD0 /* Update.swift */; }; BB6CE3BA1DC4CD3F00295C55 /* ObservableType.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE4DF3F1DA3A88C005EC162 /* ObservableType.swift */; }; BB6CE3BB1DC4CD4800295C55 /* ChangesSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9D73E51DBB727300E20D67 /* ChangesSource.swift */; }; BB6CE3BC1DC4CD6500295C55 /* TransactionalThing.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9D73DF1DBB61C100E20D67 /* TransactionalThing.swift */; }; BB6CE3BD1DC4CD6900295C55 /* ValueChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE4DF441DA3A908005EC162 /* ValueChange.swift */; }; BB6CE3BE1DC4CD6B00295C55 /* ObservableValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B291C8FCC810050DDA9 /* ObservableValue.swift */; }; BB6CE3BF1DC4CD6F00295C55 /* UpdatableValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B331C8FCC820050DDA9 /* UpdatableValue.swift */; }; BB6CE3C01DC4CD7200295C55 /* TwoWayBinding.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9D73EE1DBDFEE400E20D67 /* TwoWayBinding.swift */; }; BB6CE3C11DC4CD7600295C55 /* Variable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B351C8FCC820050DDA9 /* Variable.swift */; }; BB6CE3C21DC4CD7A00295C55 /* BufferedValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B281C8FCC810050DDA9 /* BufferedValue.swift */; }; BB6CE3C31DC4CD7E00295C55 /* DistinctValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E271DAAD2B900E85FB9 /* DistinctValue.swift */; }; BB6CE3C41DC4CD8300295C55 /* CompositeObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E221DAAD26200E85FB9 /* CompositeObservable.swift */; }; BB6CE3C51DC4CD8700295C55 /* CompositeUpdatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E2C1DAAD4BC00E85FB9 /* CompositeUpdatable.swift */; }; BB6CE3C61DC4CD8D00295C55 /* ValueMappingForValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E0E1DAAC33700E85FB9 /* ValueMappingForValue.swift */; }; BB6CE3C71DC4D09500295C55 /* ValueMappingForSourceField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E181DAAC50900E85FB9 /* ValueMappingForSourceField.swift */; }; BB6CE3C81DC4D2D400295C55 /* ValueMappingForValueField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B2C1C8FCC810050DDA9 /* ValueMappingForValueField.swift */; }; BB6CE3CA1DC4DF4400295C55 /* CombinedObservableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E311DAAD66500E85FB9 /* CombinedObservableTests.swift */; }; BB6CE3CB1DC4DF4800295C55 /* CombinedUpdatableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E351DAAD74600E85FB9 /* CombinedUpdatableTests.swift */; }; BB6CE3CC1DC4E48200295C55 /* TypeHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E391DABC45000E85FB9 /* TypeHelperTests.swift */; }; BB6CE3CE1DC4E4B100295C55 /* Type Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBAA827C1C91D43700586903 /* Type Helpers.swift */; }; BB6CE3D01DC4E67700295C55 /* TimerSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55BA51C8FCE2C0050DDA9 /* TimerSourceTests.swift */; }; BB6CE3D11DC4E72100295C55 /* KVOSupportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B9A1C8FCE2C0050DDA9 /* KVOSupportTests.swift */; }; BB6CE3D21DC4E73600295C55 /* NotificationCenterSupportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B9C1C8FCE2C0050DDA9 /* NotificationCenterSupportTests.swift */; }; BB6CE3D31DC4E79400295C55 /* ValueMappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55BA01C8FCE2C0050DDA9 /* ValueMappingTests.swift */; }; BB6CE3D41DC4F17700295C55 /* ArrayChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B1F1C8FCC810050DDA9 /* ArrayChange.swift */; }; BB6CE3D51DC4F17A00295C55 /* ArrayChangeSeparation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1657E61D71D65900094BAC /* ArrayChangeSeparation.swift */; }; BB6CE3D61DC4F18B00295C55 /* ArrayModificationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBA033651DA7CFFE00E83AC1 /* ArrayModificationTests.swift */; }; BB6CE3D71DC4F18B00295C55 /* ArrayChangeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBA033691DA7D03300E83AC1 /* ArrayChangeTests.swift */; }; BB6CE3D81DC4F18B00295C55 /* ArrayChangeSeparationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBC36AE61DA83E0E00AB3E9D /* ArrayChangeSeparationTests.swift */; }; BB6CE3D91DC4F19D00295C55 /* ObservableArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B2A1C8FCC810050DDA9 /* ObservableArray.swift */; }; BB6CE3DA1DC4F2FB00295C55 /* SetChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB8990021D5DF2D20097F350 /* SetChange.swift */; }; BB6CE3DB1DC4F2FB00295C55 /* ObservableSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB8990091D5DF88C0097F350 /* ObservableSet.swift */; }; BB7E04C81DA7233B00BE3051 /* ArrayMappingForArrayField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7E04C71DA7233B00BE3051 /* ArrayMappingForArrayField.swift */; }; BB7E04CA1DA7233B00BE3051 /* ArrayMappingForArrayField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7E04C71DA7233B00BE3051 /* ArrayMappingForArrayField.swift */; }; BB7E04CB1DA7233B00BE3051 /* ArrayMappingForArrayField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7E04C71DA7233B00BE3051 /* ArrayMappingForArrayField.swift */; }; BB7E04CF1DA723F300BE3051 /* SetMappingForValueField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7E04CC1DA723F300BE3051 /* SetMappingForValueField.swift */; }; BB7E04D01DA723F300BE3051 /* SetMappingForValueField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7E04CC1DA723F300BE3051 /* SetMappingForValueField.swift */; }; BB7E04D41DA7240C00BE3051 /* SetMappingForSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7E04D11DA7240C00BE3051 /* SetMappingForSequence.swift */; }; BB7E04D51DA7240C00BE3051 /* SetMappingForSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7E04D11DA7240C00BE3051 /* SetMappingForSequence.swift */; }; BB7E04D91DA7242F00BE3051 /* SetMappingForSetField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7E04D61DA7242F00BE3051 /* SetMappingForSetField.swift */; }; BB7E04DA1DA7242F00BE3051 /* SetMappingForSetField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7E04D61DA7242F00BE3051 /* SetMappingForSetField.swift */; }; BB7E04DE1DA7244A00BE3051 /* SetMappingForArrayField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7E04DB1DA7244A00BE3051 /* SetMappingForArrayField.swift */; }; BB7E04DF1DA7244A00BE3051 /* SetMappingForArrayField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7E04DB1DA7244A00BE3051 /* SetMappingForArrayField.swift */; }; BB7E04E11DA7255D00BE3051 /* ArrayFilteringIndexmap.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7E04E01DA7255D00BE3051 /* ArrayFilteringIndexmap.swift */; }; BB7E04E31DA7255D00BE3051 /* ArrayFilteringIndexmap.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7E04E01DA7255D00BE3051 /* ArrayFilteringIndexmap.swift */; }; BB7E04E41DA7255D00BE3051 /* ArrayFilteringIndexmap.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7E04E01DA7255D00BE3051 /* ArrayFilteringIndexmap.swift */; }; BB7E04E61DA725A500BE3051 /* ArrayFilteringOnObservableBool.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7E04E51DA725A500BE3051 /* ArrayFilteringOnObservableBool.swift */; }; BB7E04E81DA725A500BE3051 /* ArrayFilteringOnObservableBool.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7E04E51DA725A500BE3051 /* ArrayFilteringOnObservableBool.swift */; }; BB7E04E91DA725A500BE3051 /* ArrayFilteringOnObservableBool.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7E04E51DA725A500BE3051 /* ArrayFilteringOnObservableBool.swift */; }; BB8195B21DD256B700644668 /* UIControl Glue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBC3C84D1C93164200E10D59 /* UIControl Glue.swift */; }; BB8195B31DD256B700644668 /* UIBarButtonItem Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB318F6A1CB9922F0086EE83 /* UIBarButtonItem Extensions.swift */; }; BB8195B41DD256B700644668 /* UIDevice Glue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB4F2511C9594A9002532CC /* UIDevice Glue.swift */; }; BB8195B51DD256B700644668 /* UIGestureRecognizer Glue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB4F26E1C99E6CB002532CC /* UIGestureRecognizer Glue.swift */; }; BB8195B61DD256B700644668 /* CADisplayLink Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB67E5511C9B6A63002B0AB5 /* CADisplayLink Extensions.swift */; }; BB8195B71DD256B800644668 /* UIControl Glue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBC3C84D1C93164200E10D59 /* UIControl Glue.swift */; }; BB8195B81DD256B800644668 /* UIBarButtonItem Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB318F6A1CB9922F0086EE83 /* UIBarButtonItem Extensions.swift */; }; BB8195B91DD256B800644668 /* UIDevice Glue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB4F2511C9594A9002532CC /* UIDevice Glue.swift */; }; BB8195BA1DD256B800644668 /* UIGestureRecognizer Glue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB4F26E1C99E6CB002532CC /* UIGestureRecognizer Glue.swift */; }; BB8195BB1DD256B800644668 /* CADisplayLink Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB67E5511C9B6A63002B0AB5 /* CADisplayLink Extensions.swift */; }; BB8195BC1DD256B900644668 /* UIControl Glue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBC3C84D1C93164200E10D59 /* UIControl Glue.swift */; }; BB8195BD1DD256B900644668 /* UIBarButtonItem Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB318F6A1CB9922F0086EE83 /* UIBarButtonItem Extensions.swift */; }; BB8195BE1DD256B900644668 /* UIDevice Glue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB4F2511C9594A9002532CC /* UIDevice Glue.swift */; }; BB8195BF1DD256B900644668 /* UIGestureRecognizer Glue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB4F26E1C99E6CB002532CC /* UIGestureRecognizer Glue.swift */; }; BB8195C01DD257A300644668 /* ConnectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E511DAC1B1300E85FB9 /* ConnectorTests.swift */; }; BB82E3E11D98F34E0035DDAD /* ArrayFilteringOnPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB82E3E01D98F34E0035DDAD /* ArrayFilteringOnPredicate.swift */; }; BB82E3E31D98F34E0035DDAD /* ArrayFilteringOnPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB82E3E01D98F34E0035DDAD /* ArrayFilteringOnPredicate.swift */; }; BB82E3E41D98F34E0035DDAD /* ArrayFilteringOnPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB82E3E01D98F34E0035DDAD /* ArrayFilteringOnPredicate.swift */; }; BB88C40E1DC3A08B00C7EC3C /* ObservableTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB88C40D1DC3A08B00C7EC3C /* ObservableTypeTests.swift */; }; BB88C40F1DC3A08B00C7EC3C /* ObservableTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB88C40D1DC3A08B00C7EC3C /* ObservableTypeTests.swift */; }; BB88C4101DC3A08B00C7EC3C /* ObservableTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB88C40D1DC3A08B00C7EC3C /* ObservableTypeTests.swift */; }; BB88C4111DC3A84C00C7EC3C /* VariableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55BA71C8FCE2C0050DDA9 /* VariableTests.swift */; }; BB88C4131DC3B65F00C7EC3C /* ValueBufferingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB88C4121DC3B65F00C7EC3C /* ValueBufferingTests.swift */; }; BB88C4141DC3B65F00C7EC3C /* ValueBufferingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB88C4121DC3B65F00C7EC3C /* ValueBufferingTests.swift */; }; BB88C4151DC3B65F00C7EC3C /* ValueBufferingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB88C4121DC3B65F00C7EC3C /* ValueBufferingTests.swift */; }; BB8990271D5F84150097F350 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB8990251D5F84050097F350 /* Foundation.framework */; }; BB8990291D5F84210097F350 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB8990281D5F84210097F350 /* Foundation.framework */; }; BB89902B1D5F842C0097F350 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB89902A1D5F842C0097F350 /* Foundation.framework */; }; BB89902D1D5F84340097F350 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB89902C1D5F84340097F350 /* Foundation.framework */; }; BB8D366E1DA116CB000D44C5 /* Bookshelf.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB8D366D1DA116CB000D44C5 /* Bookshelf.swift */; }; BB8D36701DA116CB000D44C5 /* Bookshelf.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB8D366D1DA116CB000D44C5 /* Bookshelf.swift */; }; BB948BBC1DD5EFC900B0734C /* ComputedUpdatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB948BBB1DD5EFC900B0734C /* ComputedUpdatable.swift */; }; BB948BBD1DD5EFC900B0734C /* ComputedUpdatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB948BBB1DD5EFC900B0734C /* ComputedUpdatable.swift */; }; BB948BBE1DD5EFC900B0734C /* ComputedUpdatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB948BBB1DD5EFC900B0734C /* ComputedUpdatable.swift */; }; BB948BBF1DD5EFC900B0734C /* ComputedUpdatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB948BBB1DD5EFC900B0734C /* ComputedUpdatable.swift */; }; BB948BC11DD720FE00B0734C /* UISwitch Glue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB948BC01DD720FE00B0734C /* UISwitch Glue.swift */; }; BB948BC21DD7213200B0734C /* UISwitch Glue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB948BC01DD720FE00B0734C /* UISwitch Glue.swift */; }; BB948BC31DD7213200B0734C /* UISwitch Glue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB948BC01DD720FE00B0734C /* UISwitch Glue.swift */; }; BB948BC41DD7213300B0734C /* UISwitch Glue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB948BC01DD720FE00B0734C /* UISwitch Glue.swift */; }; BB9D73C91DBB2EB400E20D67 /* Connect.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9D73C81DBB2EB400E20D67 /* Connect.swift */; }; BB9D73CA1DBB2EB400E20D67 /* Connect.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9D73C81DBB2EB400E20D67 /* Connect.swift */; }; BB9D73CB1DBB2EB400E20D67 /* Connect.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9D73C81DBB2EB400E20D67 /* Connect.swift */; }; BB9D73CC1DBB2EB400E20D67 /* Connect.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9D73C81DBB2EB400E20D67 /* Connect.swift */; }; BB9D73CF1DBB38F900E20D67 /* Sink.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9D73CE1DBB38F900E20D67 /* Sink.swift */; }; BB9D73D01DBB38F900E20D67 /* Sink.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9D73CE1DBB38F900E20D67 /* Sink.swift */; }; BB9D73D11DBB38F900E20D67 /* Sink.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9D73CE1DBB38F900E20D67 /* Sink.swift */; }; BB9D73D21DBB38F900E20D67 /* Sink.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9D73CE1DBB38F900E20D67 /* Sink.swift */; }; BB9D73D31DBB3DEA00E20D67 /* Signal.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B2E1C8FCC810050DDA9 /* Signal.swift */; }; BB9D73D41DBB4A3700E20D67 /* SimpleSources.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B2F1C8FCC810050DDA9 /* SimpleSources.swift */; }; BB9D73D51DBB4BB300E20D67 /* MergedSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B261C8FCC810050DDA9 /* MergedSource.swift */; }; BB9D73D61DBB519500E20D67 /* TransformedSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B311C8FCC810050DDA9 /* TransformedSource.swift */; }; BB9D73DC1DBB612600E20D67 /* Connector.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B221C8FCC810050DDA9 /* Connector.swift */; }; BB9D73E01DBB61C100E20D67 /* TransactionalThing.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9D73DF1DBB61C100E20D67 /* TransactionalThing.swift */; }; BB9D73E21DBB61C100E20D67 /* TransactionalThing.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9D73DF1DBB61C100E20D67 /* TransactionalThing.swift */; }; BB9D73E31DBB61C100E20D67 /* TransactionalThing.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9D73DF1DBB61C100E20D67 /* TransactionalThing.swift */; }; BB9D73E61DBB727300E20D67 /* ChangesSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9D73E51DBB727300E20D67 /* ChangesSource.swift */; }; BB9D73E81DBB727300E20D67 /* ChangesSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9D73E51DBB727300E20D67 /* ChangesSource.swift */; }; BB9D73E91DBB727300E20D67 /* ChangesSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9D73E51DBB727300E20D67 /* ChangesSource.swift */; }; BB9D73EF1DBDFEE400E20D67 /* TwoWayBinding.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9D73EE1DBDFEE400E20D67 /* TwoWayBinding.swift */; }; BB9D73F11DBDFEE400E20D67 /* TwoWayBinding.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9D73EE1DBDFEE400E20D67 /* TwoWayBinding.swift */; }; BB9D73F21DBDFEE400E20D67 /* TwoWayBinding.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9D73EE1DBDFEE400E20D67 /* TwoWayBinding.swift */; }; BB9D73F41DBE03EB00E20D67 /* DispatchSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9D73F31DBE03EB00E20D67 /* DispatchSource.swift */; }; BB9D73F51DBE03EB00E20D67 /* DispatchSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9D73F31DBE03EB00E20D67 /* DispatchSource.swift */; }; BB9D73F61DBE03EB00E20D67 /* DispatchSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9D73F31DBE03EB00E20D67 /* DispatchSource.swift */; }; BB9D73F71DBE03EB00E20D67 /* DispatchSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9D73F31DBE03EB00E20D67 /* DispatchSource.swift */; }; BB9D73F81DBE041A00E20D67 /* TimerSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B321C8FCC820050DDA9 /* TimerSource.swift */; }; BB9D73FE1DBE061D00E20D67 /* OwnedSink.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9D73FD1DBE061D00E20D67 /* OwnedSink.swift */; }; BB9D73FF1DBE061D00E20D67 /* OwnedSink.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9D73FD1DBE061D00E20D67 /* OwnedSink.swift */; }; BB9D74001DBE061D00E20D67 /* OwnedSink.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9D73FD1DBE061D00E20D67 /* OwnedSink.swift */; }; BB9D74011DBE061D00E20D67 /* OwnedSink.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9D73FD1DBE061D00E20D67 /* OwnedSink.swift */; }; BB9D741E1DBE644200E20D67 /* NSNotificationCenter Support.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B271C8FCC810050DDA9 /* NSNotificationCenter Support.swift */; }; BB9D74201DBE654600E20D67 /* NSObject Glue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBC2A8281CBC14C600394D24 /* NSObject Glue.swift */; }; BB9D74241DBE7B3700E20D67 /* ObservableSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB8990091D5DF88C0097F350 /* ObservableSet.swift */; }; BB9D74261DBE7B3B00E20D67 /* UpdatableSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB89901A1D5F6AAE0097F350 /* UpdatableSet.swift */; }; BB9D74271DBE7B3B00E20D67 /* SetVariable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB89901F1D5F6F980097F350 /* SetVariable.swift */; }; BB9D74281DBE7B3B00E20D67 /* ObservableContains.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBF400631DA108E900DA0B2C /* ObservableContains.swift */; }; BB9D74291DBE7B3B00E20D67 /* SetMappingBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE4DF5B1DA55DA3005EC162 /* SetMappingBase.swift */; }; BB9D742A1DBE7B3B00E20D67 /* SetMappingForValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE4DF561DA55384005EC162 /* SetMappingForValue.swift */; }; BB9D742B1DBE7B3B00E20D67 /* SetMappingForSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7E04D11DA7240C00BE3051 /* SetMappingForSequence.swift */; }; BB9D742C1DBE7B3B00E20D67 /* SetMappingForValueField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7E04CC1DA723F300BE3051 /* SetMappingForValueField.swift */; }; BB9D742D1DBE7B3B00E20D67 /* SetMappingForSetField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7E04D61DA7242F00BE3051 /* SetMappingForSetField.swift */; }; BB9D742E1DBE7B3B00E20D67 /* SetMappingForArrayField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7E04DB1DA7244A00BE3051 /* SetMappingForArrayField.swift */; }; BB9D742F1DBE7B3B00E20D67 /* SetFilteringOnPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B231C8FCC810050DDA9 /* SetFilteringOnPredicate.swift */; }; BB9D74301DBE7B3B00E20D67 /* SetFilteringOnObservableBool.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBA033561DA726AA00E83AC1 /* SetFilteringOnObservableBool.swift */; }; BB9D74311DBE7B3B00E20D67 /* SetSortingByComparator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBA0335B1DA7276200E83AC1 /* SetSortingByComparator.swift */; }; BB9D74321DBE7B3B00E20D67 /* SetSortingByMappingToComparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1E4D691D61EA5200F1831F /* SetSortingByMappingToComparable.swift */; }; BB9D74331DBE7B3B00E20D67 /* SetSortingByMappingToObservableComparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBA033601DA7278C00E83AC1 /* SetSortingByMappingToObservableComparable.swift */; }; BB9D74341DBE7B3B00E20D67 /* SetFolding.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E011DAA910500E85FB9 /* SetFolding.swift */; }; BB9D74351DBE7B3B00E20D67 /* SetReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3D92951D647E83003BEDBF /* SetReference.swift */; }; BB9D74451DBEB5F300E20D67 /* DistinctUnion.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE4DF491DA409EE005EC162 /* DistinctUnion.swift */; }; BB9D744A1DBEC82C00E20D67 /* ValueMappingForSetField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E1D1DAAC7E600E85FB9 /* ValueMappingForSetField.swift */; }; BB9FCDCC1F5E54BF001B8781 /* NSButton Glue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9FCDCB1F5E54BF001B8781 /* NSButton Glue.swift */; }; BB9FCDCE1F5EB646001B8781 /* NSControl Glue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9FCDCD1F5EB646001B8781 /* NSControl Glue.swift */; }; BB9FCDD01F5ECFF3001B8781 /* NSPopUpButton Glue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9FCDCF1F5ECFF3001B8781 /* NSPopUpButton Glue.swift */; }; BBA033591DA726AA00E83AC1 /* SetFilteringOnObservableBool.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBA033561DA726AA00E83AC1 /* SetFilteringOnObservableBool.swift */; }; BBA0335A1DA726AA00E83AC1 /* SetFilteringOnObservableBool.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBA033561DA726AA00E83AC1 /* SetFilteringOnObservableBool.swift */; }; BBA0335E1DA7276200E83AC1 /* SetSortingByComparator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBA0335B1DA7276200E83AC1 /* SetSortingByComparator.swift */; }; BBA0335F1DA7276200E83AC1 /* SetSortingByComparator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBA0335B1DA7276200E83AC1 /* SetSortingByComparator.swift */; }; BBA033631DA7278C00E83AC1 /* SetSortingByMappingToObservableComparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBA033601DA7278C00E83AC1 /* SetSortingByMappingToObservableComparable.swift */; }; BBA033641DA7278C00E83AC1 /* SetSortingByMappingToObservableComparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBA033601DA7278C00E83AC1 /* SetSortingByMappingToObservableComparable.swift */; }; BBA033681DA7CFFE00E83AC1 /* ArrayModificationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBA033651DA7CFFE00E83AC1 /* ArrayModificationTests.swift */; }; BBA0336C1DA7D03300E83AC1 /* ArrayChangeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBA033691DA7D03300E83AC1 /* ArrayChangeTests.swift */; }; BBAA827D1C91D43700586903 /* Type Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBAA827C1C91D43700586903 /* Type Helpers.swift */; }; BBAA827F1C91D43700586903 /* Type Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBAA827C1C91D43700586903 /* Type Helpers.swift */; }; BBAA82801C91D43700586903 /* Type Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBAA827C1C91D43700586903 /* Type Helpers.swift */; }; BBB4F2521C9594A9002532CC /* UIDevice Glue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB4F2511C9594A9002532CC /* UIDevice Glue.swift */; }; BBB4F26F1C99E6CC002532CC /* UIGestureRecognizer Glue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB4F26E1C99E6CB002532CC /* UIGestureRecognizer Glue.swift */; }; BBB55B081C8F8CBC0050DDA9 /* GlueKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BBB55AFE1C8F8CBB0050DDA9 /* GlueKit.framework */; }; BBB55B421C8FCC820050DDA9 /* Connector.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B221C8FCC810050DDA9 /* Connector.swift */; }; BBB55B441C8FCC820050DDA9 /* Connector.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B221C8FCC810050DDA9 /* Connector.swift */; }; BBB55B451C8FCC820050DDA9 /* Connector.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B221C8FCC810050DDA9 /* Connector.swift */; }; BBB55B4E1C8FCC820050DDA9 /* Locks.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B251C8FCC810050DDA9 /* Locks.swift */; }; BBB55B4F1C8FCC820050DDA9 /* Locks.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B251C8FCC810050DDA9 /* Locks.swift */; }; BBB55B501C8FCC820050DDA9 /* Locks.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B251C8FCC810050DDA9 /* Locks.swift */; }; BBB55B511C8FCC820050DDA9 /* Locks.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B251C8FCC810050DDA9 /* Locks.swift */; }; BBB55B521C8FCC820050DDA9 /* MergedSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B261C8FCC810050DDA9 /* MergedSource.swift */; }; BBB55B541C8FCC820050DDA9 /* MergedSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B261C8FCC810050DDA9 /* MergedSource.swift */; }; BBB55B551C8FCC820050DDA9 /* MergedSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B261C8FCC810050DDA9 /* MergedSource.swift */; }; BBB55B561C8FCC820050DDA9 /* NSNotificationCenter Support.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B271C8FCC810050DDA9 /* NSNotificationCenter Support.swift */; }; BBB55B581C8FCC820050DDA9 /* NSNotificationCenter Support.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B271C8FCC810050DDA9 /* NSNotificationCenter Support.swift */; }; BBB55B591C8FCC820050DDA9 /* NSNotificationCenter Support.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B271C8FCC810050DDA9 /* NSNotificationCenter Support.swift */; }; BBB55B5A1C8FCC820050DDA9 /* BufferedValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B281C8FCC810050DDA9 /* BufferedValue.swift */; }; BBB55B5C1C8FCC820050DDA9 /* BufferedValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B281C8FCC810050DDA9 /* BufferedValue.swift */; }; BBB55B5D1C8FCC820050DDA9 /* BufferedValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B281C8FCC810050DDA9 /* BufferedValue.swift */; }; BBB55B5E1C8FCC820050DDA9 /* ObservableValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B291C8FCC810050DDA9 /* ObservableValue.swift */; }; BBB55B601C8FCC820050DDA9 /* ObservableValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B291C8FCC810050DDA9 /* ObservableValue.swift */; }; BBB55B611C8FCC820050DDA9 /* ObservableValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B291C8FCC810050DDA9 /* ObservableValue.swift */; }; BBB55B661C8FCC820050DDA9 /* Reference.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B2B1C8FCC810050DDA9 /* Reference.swift */; }; BBB55B671C8FCC820050DDA9 /* Reference.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B2B1C8FCC810050DDA9 /* Reference.swift */; }; BBB55B681C8FCC820050DDA9 /* Reference.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B2B1C8FCC810050DDA9 /* Reference.swift */; }; BBB55B691C8FCC820050DDA9 /* Reference.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B2B1C8FCC810050DDA9 /* Reference.swift */; }; BBB55B721C8FCC820050DDA9 /* Signal.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B2E1C8FCC810050DDA9 /* Signal.swift */; }; BBB55B741C8FCC820050DDA9 /* Signal.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B2E1C8FCC810050DDA9 /* Signal.swift */; }; BBB55B751C8FCC820050DDA9 /* Signal.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B2E1C8FCC810050DDA9 /* Signal.swift */; }; BBB55B761C8FCC820050DDA9 /* SimpleSources.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B2F1C8FCC810050DDA9 /* SimpleSources.swift */; }; BBB55B781C8FCC820050DDA9 /* SimpleSources.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B2F1C8FCC810050DDA9 /* SimpleSources.swift */; }; BBB55B791C8FCC820050DDA9 /* SimpleSources.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B2F1C8FCC810050DDA9 /* SimpleSources.swift */; }; BBB55B7A1C8FCC820050DDA9 /* Source.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B301C8FCC810050DDA9 /* Source.swift */; }; BBB55B7B1C8FCC820050DDA9 /* Source.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B301C8FCC810050DDA9 /* Source.swift */; }; BBB55B7C1C8FCC820050DDA9 /* Source.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B301C8FCC810050DDA9 /* Source.swift */; }; BBB55B7D1C8FCC820050DDA9 /* Source.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B301C8FCC810050DDA9 /* Source.swift */; }; BBB55B7E1C8FCC820050DDA9 /* TransformedSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B311C8FCC810050DDA9 /* TransformedSource.swift */; }; BBB55B801C8FCC820050DDA9 /* TransformedSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B311C8FCC810050DDA9 /* TransformedSource.swift */; }; BBB55B811C8FCC820050DDA9 /* TransformedSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B311C8FCC810050DDA9 /* TransformedSource.swift */; }; BBB55B821C8FCC820050DDA9 /* TimerSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B321C8FCC820050DDA9 /* TimerSource.swift */; }; BBB55B841C8FCC820050DDA9 /* TimerSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B321C8FCC820050DDA9 /* TimerSource.swift */; }; BBB55B851C8FCC820050DDA9 /* TimerSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B321C8FCC820050DDA9 /* TimerSource.swift */; }; BBB55B861C8FCC820050DDA9 /* UpdatableValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B331C8FCC820050DDA9 /* UpdatableValue.swift */; }; BBB55B881C8FCC820050DDA9 /* UpdatableValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B331C8FCC820050DDA9 /* UpdatableValue.swift */; }; BBB55B891C8FCC820050DDA9 /* UpdatableValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B331C8FCC820050DDA9 /* UpdatableValue.swift */; }; BBB55B8E1C8FCC820050DDA9 /* Variable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B351C8FCC820050DDA9 /* Variable.swift */; }; BBB55B901C8FCC820050DDA9 /* Variable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B351C8FCC820050DDA9 /* Variable.swift */; }; BBB55B911C8FCC820050DDA9 /* Variable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B351C8FCC820050DDA9 /* Variable.swift */; }; BBB55BAA1C8FCE2C0050DDA9 /* ArrayVariableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B981C8FCE2C0050DDA9 /* ArrayVariableTests.swift */; }; BBB55BAE1C8FCE2C0050DDA9 /* KVOSupportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B9A1C8FCE2C0050DDA9 /* KVOSupportTests.swift */; }; BBB55BB01C8FCE2C0050DDA9 /* KVOSupportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B9A1C8FCE2C0050DDA9 /* KVOSupportTests.swift */; }; BBB55BB11C8FCE2C0050DDA9 /* MergedSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B9B1C8FCE2C0050DDA9 /* MergedSourceTests.swift */; }; BBB55BB31C8FCE2C0050DDA9 /* MergedSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B9B1C8FCE2C0050DDA9 /* MergedSourceTests.swift */; }; BBB55BB41C8FCE2C0050DDA9 /* NotificationCenterSupportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B9C1C8FCE2C0050DDA9 /* NotificationCenterSupportTests.swift */; }; BBB55BB61C8FCE2C0050DDA9 /* NotificationCenterSupportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B9C1C8FCE2C0050DDA9 /* NotificationCenterSupportTests.swift */; }; BBB55BB71C8FCE2C0050DDA9 /* ObservableValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B9D1C8FCE2C0050DDA9 /* ObservableValueTests.swift */; }; BBB55BB91C8FCE2C0050DDA9 /* ObservableValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B9D1C8FCE2C0050DDA9 /* ObservableValueTests.swift */; }; BBB55BBA1C8FCE2C0050DDA9 /* DistinctTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B9E1C8FCE2C0050DDA9 /* DistinctTests.swift */; }; BBB55BBC1C8FCE2C0050DDA9 /* DistinctTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B9E1C8FCE2C0050DDA9 /* DistinctTests.swift */; }; BBB55BBF1C8FCE2C0050DDA9 /* ArrayMappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B9F1C8FCE2C0050DDA9 /* ArrayMappingTests.swift */; }; BBB55BC01C8FCE2C0050DDA9 /* ValueMappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55BA01C8FCE2C0050DDA9 /* ValueMappingTests.swift */; }; BBB55BC21C8FCE2C0050DDA9 /* ValueMappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55BA01C8FCE2C0050DDA9 /* ValueMappingTests.swift */; }; BBB55BC31C8FCE2C0050DDA9 /* SignalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55BA11C8FCE2C0050DDA9 /* SignalTests.swift */; }; BBB55BC51C8FCE2C0050DDA9 /* SignalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55BA11C8FCE2C0050DDA9 /* SignalTests.swift */; }; BBB55BC61C8FCE2C0050DDA9 /* SimpleSourcesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55BA21C8FCE2C0050DDA9 /* SimpleSourcesTests.swift */; }; BBB55BC81C8FCE2C0050DDA9 /* SimpleSourcesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55BA21C8FCE2C0050DDA9 /* SimpleSourcesTests.swift */; }; BBB55BC91C8FCE2C0050DDA9 /* SourceOperatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55BA31C8FCE2C0050DDA9 /* SourceOperatorTests.swift */; }; BBB55BCB1C8FCE2C0050DDA9 /* SourceOperatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55BA31C8FCE2C0050DDA9 /* SourceOperatorTests.swift */; }; BBB55BCC1C8FCE2C0050DDA9 /* TestUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55BA41C8FCE2C0050DDA9 /* TestUtilities.swift */; }; BBB55BCD1C8FCE2C0050DDA9 /* TestUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55BA41C8FCE2C0050DDA9 /* TestUtilities.swift */; }; BBB55BCE1C8FCE2C0050DDA9 /* TestUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55BA41C8FCE2C0050DDA9 /* TestUtilities.swift */; }; BBB55BCF1C8FCE2C0050DDA9 /* TimerSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55BA51C8FCE2C0050DDA9 /* TimerSourceTests.swift */; }; BBB55BD11C8FCE2C0050DDA9 /* TimerSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55BA51C8FCE2C0050DDA9 /* TimerSourceTests.swift */; }; BBB55BD21C8FCE2C0050DDA9 /* TwoWayBindingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55BA61C8FCE2C0050DDA9 /* TwoWayBindingTests.swift */; }; BBB55BD41C8FCE2C0050DDA9 /* TwoWayBindingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55BA61C8FCE2C0050DDA9 /* TwoWayBindingTests.swift */; }; BBB55BD51C8FCE2C0050DDA9 /* VariableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55BA71C8FCE2C0050DDA9 /* VariableTests.swift */; }; BBB55BD71C8FCE2C0050DDA9 /* VariableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55BA71C8FCE2C0050DDA9 /* VariableTests.swift */; }; BBBBEC8D1DCA1AEB000B646D /* ValueReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBBEC8C1DCA1AEB000B646D /* ValueReference.swift */; }; BBBBEC8E1DCA1AEB000B646D /* ValueReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBBEC8C1DCA1AEB000B646D /* ValueReference.swift */; }; BBBBEC8F1DCA1AEB000B646D /* ValueReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBBEC8C1DCA1AEB000B646D /* ValueReference.swift */; }; BBBBEC901DCA1AEB000B646D /* ValueReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBBEC8C1DCA1AEB000B646D /* ValueReference.swift */; }; BBBBEC921DCA1D43000B646D /* ValueReferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBBEC911DCA1D43000B646D /* ValueReferenceTests.swift */; }; BBBBEC931DCA1D43000B646D /* ValueReferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBBEC911DCA1D43000B646D /* ValueReferenceTests.swift */; }; BBBBEC941DCA1D43000B646D /* ValueReferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBBEC911DCA1D43000B646D /* ValueReferenceTests.swift */; }; BBBBEC961DCA1FEB000B646D /* ArrayReferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBBEC951DCA1FEB000B646D /* ArrayReferenceTests.swift */; }; BBBBEC971DCA1FEB000B646D /* ArrayReferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBBEC951DCA1FEB000B646D /* ArrayReferenceTests.swift */; }; BBBBEC981DCA1FEB000B646D /* ArrayReferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBBEC951DCA1FEB000B646D /* ArrayReferenceTests.swift */; }; BBBBEC9A1DCA223A000B646D /* SetReferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBBEC991DCA223A000B646D /* SetReferenceTests.swift */; }; BBBBEC9B1DCA223A000B646D /* SetReferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBBEC991DCA223A000B646D /* SetReferenceTests.swift */; }; BBBBEC9C1DCA223A000B646D /* SetReferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBBEC991DCA223A000B646D /* SetReferenceTests.swift */; }; BBBBEC9E1DCA2FAB000B646D /* NSUserDefaultsSupportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBBEC9D1DCA2FAB000B646D /* NSUserDefaultsSupportTests.swift */; }; BBBBEC9F1DCA2FAB000B646D /* NSUserDefaultsSupportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBBEC9D1DCA2FAB000B646D /* NSUserDefaultsSupportTests.swift */; }; BBBBECA01DCA2FAB000B646D /* NSUserDefaultsSupportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBBEC9D1DCA2FAB000B646D /* NSUserDefaultsSupportTests.swift */; }; BBBBECA21DCA3AA8000B646D /* DispatchSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBBECA11DCA3AA8000B646D /* DispatchSourceTests.swift */; }; BBBBECA31DCA3AA8000B646D /* DispatchSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBBECA11DCA3AA8000B646D /* DispatchSourceTests.swift */; }; BBBBECA41DCA3AA8000B646D /* DispatchSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBBECA11DCA3AA8000B646D /* DispatchSourceTests.swift */; }; BBBBECA61DCA3FF0000B646D /* BufferedSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBBECA51DCA3FF0000B646D /* BufferedSet.swift */; }; BBBBECA71DCA3FF0000B646D /* BufferedSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBBECA51DCA3FF0000B646D /* BufferedSet.swift */; }; BBBBECA81DCA3FF0000B646D /* BufferedSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBBECA51DCA3FF0000B646D /* BufferedSet.swift */; }; BBBBECA91DCA3FF0000B646D /* BufferedSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBBECA51DCA3FF0000B646D /* BufferedSet.swift */; }; BBBBECAB1DCA4168000B646D /* ArrayBufferingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBBECAA1DCA4168000B646D /* ArrayBufferingTests.swift */; }; BBBBECAC1DCA4168000B646D /* ArrayBufferingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBBECAA1DCA4168000B646D /* ArrayBufferingTests.swift */; }; BBBBECAD1DCA4168000B646D /* ArrayBufferingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBBECAA1DCA4168000B646D /* ArrayBufferingTests.swift */; }; BBBBECAF1DCA446A000B646D /* SetBufferingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBBECAE1DCA446A000B646D /* SetBufferingTests.swift */; }; BBBBECB01DCA446A000B646D /* SetBufferingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBBECAE1DCA446A000B646D /* SetBufferingTests.swift */; }; BBBBECB11DCA446A000B646D /* SetBufferingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBBECAE1DCA446A000B646D /* SetBufferingTests.swift */; }; BBBFD14B1EBE673200EC9814 /* SetSortingByComparableField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBFD14A1EBE673200EC9814 /* SetSortingByComparableField.swift */; }; BBBFD14C1EBE673200EC9814 /* SetSortingByComparableField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBFD14A1EBE673200EC9814 /* SetSortingByComparableField.swift */; }; BBBFD14D1EBE673200EC9814 /* SetSortingByComparableField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBFD14A1EBE673200EC9814 /* SetSortingByComparableField.swift */; }; BBBFD14E1EBE673200EC9814 /* SetSortingByComparableField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBFD14A1EBE673200EC9814 /* SetSortingByComparableField.swift */; }; BBC2A8291CBC14C600394D24 /* NSObject Glue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBC2A8281CBC14C600394D24 /* NSObject Glue.swift */; }; BBC2A82B1CBC14C600394D24 /* NSObject Glue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBC2A8281CBC14C600394D24 /* NSObject Glue.swift */; }; BBC2A82C1CBC14C600394D24 /* NSObject Glue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBC2A8281CBC14C600394D24 /* NSObject Glue.swift */; }; BBC36AED1DA8453000AB3E9D /* ArrayChangeSeparationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBC36AE61DA83E0E00AB3E9D /* ArrayChangeSeparationTests.swift */; }; BBC3C84E1C93164200E10D59 /* UIControl Glue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBC3C84D1C93164200E10D59 /* UIControl Glue.swift */; }; BBC4AE501D9A829500FF7DE0 /* ArrayFilteringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBC4AE4D1D9A829500FF7DE0 /* ArrayFilteringTests.swift */; }; BBCD9DF51DA9104A00E85FB9 /* RefListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9DF41DA9104A00E85FB9 /* RefListTests.swift */; }; BBCD9DF61DA9104A00E85FB9 /* RefListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9DF41DA9104A00E85FB9 /* RefListTests.swift */; }; BBCD9DF71DA9104A00E85FB9 /* RefListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9DF41DA9104A00E85FB9 /* RefListTests.swift */; }; BBCD9DF91DAA885E00E85FB9 /* ArrayFolding.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9DF81DAA885E00E85FB9 /* ArrayFolding.swift */; }; BBCD9DFB1DAA885E00E85FB9 /* ArrayFolding.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9DF81DAA885E00E85FB9 /* ArrayFolding.swift */; }; BBCD9DFC1DAA885E00E85FB9 /* ArrayFolding.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9DF81DAA885E00E85FB9 /* ArrayFolding.swift */; }; BBCD9E001DAA8F7800E85FB9 /* ArrayFoldingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9DFD1DAA8F7800E85FB9 /* ArrayFoldingTests.swift */; }; BBCD9E041DAA910500E85FB9 /* SetFolding.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E011DAA910500E85FB9 /* SetFolding.swift */; }; BBCD9E051DAA910500E85FB9 /* SetFolding.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E011DAA910500E85FB9 /* SetFolding.swift */; }; BBCD9E071DAA918400E85FB9 /* SetFoldingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E061DAA918400E85FB9 /* SetFoldingTests.swift */; }; BBCD9E091DAA918400E85FB9 /* SetFoldingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E061DAA918400E85FB9 /* SetFoldingTests.swift */; }; BBCD9E0B1DAAB6E700E85FB9 /* MockUpdateSink.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E0A1DAAB6E700E85FB9 /* MockUpdateSink.swift */; }; BBCD9E0D1DAAB6E700E85FB9 /* MockUpdateSink.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E0A1DAAB6E700E85FB9 /* MockUpdateSink.swift */; }; BBCD9E0F1DAAC33700E85FB9 /* ValueMappingForValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E0E1DAAC33700E85FB9 /* ValueMappingForValue.swift */; }; BBCD9E111DAAC33700E85FB9 /* ValueMappingForValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E0E1DAAC33700E85FB9 /* ValueMappingForValue.swift */; }; BBCD9E121DAAC33700E85FB9 /* ValueMappingForValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E0E1DAAC33700E85FB9 /* ValueMappingForValue.swift */; }; BBCD9E141DAAC4DC00E85FB9 /* ValueMappingForArrayField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E131DAAC4DC00E85FB9 /* ValueMappingForArrayField.swift */; }; BBCD9E161DAAC4DC00E85FB9 /* ValueMappingForArrayField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E131DAAC4DC00E85FB9 /* ValueMappingForArrayField.swift */; }; BBCD9E171DAAC4DC00E85FB9 /* ValueMappingForArrayField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E131DAAC4DC00E85FB9 /* ValueMappingForArrayField.swift */; }; BBCD9E191DAAC50900E85FB9 /* ValueMappingForSourceField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E181DAAC50900E85FB9 /* ValueMappingForSourceField.swift */; }; BBCD9E1B1DAAC50900E85FB9 /* ValueMappingForSourceField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E181DAAC50900E85FB9 /* ValueMappingForSourceField.swift */; }; BBCD9E1C1DAAC50900E85FB9 /* ValueMappingForSourceField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E181DAAC50900E85FB9 /* ValueMappingForSourceField.swift */; }; BBCD9E201DAAC7E600E85FB9 /* ValueMappingForSetField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E1D1DAAC7E600E85FB9 /* ValueMappingForSetField.swift */; }; BBCD9E211DAAC7E600E85FB9 /* ValueMappingForSetField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E1D1DAAC7E600E85FB9 /* ValueMappingForSetField.swift */; }; BBCD9E231DAAD26200E85FB9 /* CompositeObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E221DAAD26200E85FB9 /* CompositeObservable.swift */; }; BBCD9E251DAAD26200E85FB9 /* CompositeObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E221DAAD26200E85FB9 /* CompositeObservable.swift */; }; BBCD9E261DAAD26200E85FB9 /* CompositeObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E221DAAD26200E85FB9 /* CompositeObservable.swift */; }; BBCD9E281DAAD2B900E85FB9 /* DistinctValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E271DAAD2B900E85FB9 /* DistinctValue.swift */; }; BBCD9E2A1DAAD2B900E85FB9 /* DistinctValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E271DAAD2B900E85FB9 /* DistinctValue.swift */; }; BBCD9E2B1DAAD2B900E85FB9 /* DistinctValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E271DAAD2B900E85FB9 /* DistinctValue.swift */; }; BBCD9E2D1DAAD4BC00E85FB9 /* CompositeUpdatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E2C1DAAD4BC00E85FB9 /* CompositeUpdatable.swift */; }; BBCD9E2F1DAAD4BC00E85FB9 /* CompositeUpdatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E2C1DAAD4BC00E85FB9 /* CompositeUpdatable.swift */; }; BBCD9E301DAAD4BC00E85FB9 /* CompositeUpdatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E2C1DAAD4BC00E85FB9 /* CompositeUpdatable.swift */; }; BBCD9E321DAAD66500E85FB9 /* CombinedObservableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E311DAAD66500E85FB9 /* CombinedObservableTests.swift */; }; BBCD9E341DAAD66500E85FB9 /* CombinedObservableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E311DAAD66500E85FB9 /* CombinedObservableTests.swift */; }; BBCD9E361DAAD74600E85FB9 /* CombinedUpdatableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E351DAAD74600E85FB9 /* CombinedUpdatableTests.swift */; }; BBCD9E381DAAD74600E85FB9 /* CombinedUpdatableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E351DAAD74600E85FB9 /* CombinedUpdatableTests.swift */; }; BBCD9E3A1DABC45000E85FB9 /* TypeHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E391DABC45000E85FB9 /* TypeHelperTests.swift */; }; BBCD9E3C1DABC45000E85FB9 /* TypeHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E391DABC45000E85FB9 /* TypeHelperTests.swift */; }; BBCD9E3E1DABC81A00E85FB9 /* MockSink.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E3D1DABC81A00E85FB9 /* MockSink.swift */; }; BBCD9E401DABC81A00E85FB9 /* MockSink.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E3D1DABC81A00E85FB9 /* MockSink.swift */; }; BBCD9E421DABD4BE00E85FB9 /* ObservableSetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E411DABD4BE00E85FB9 /* ObservableSetTests.swift */; }; BBCD9E441DABD4BE00E85FB9 /* ObservableSetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E411DABD4BE00E85FB9 /* ObservableSetTests.swift */; }; BBCD9E481DABEA5500E85FB9 /* ObservableArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E451DABEA5500E85FB9 /* ObservableArrayTests.swift */; }; BBCD9E4C1DABFA2900E85FB9 /* ArrayConcatenationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E491DABFA2900E85FB9 /* ArrayConcatenationTests.swift */; }; BBCD9E4E1DABFE8800E85FB9 /* SetVariableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E4D1DABFE8800E85FB9 /* SetVariableTests.swift */; }; BBCD9E501DABFE8800E85FB9 /* SetVariableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E4D1DABFE8800E85FB9 /* SetVariableTests.swift */; }; BBCD9E521DAC1B1300E85FB9 /* ConnectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E511DAC1B1300E85FB9 /* ConnectorTests.swift */; }; BBCD9E541DAC1B1300E85FB9 /* ConnectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E511DAC1B1300E85FB9 /* ConnectorTests.swift */; }; BBE4DF401DA3A88C005EC162 /* ObservableType.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE4DF3F1DA3A88C005EC162 /* ObservableType.swift */; }; BBE4DF421DA3A88C005EC162 /* ObservableType.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE4DF3F1DA3A88C005EC162 /* ObservableType.swift */; }; BBE4DF431DA3A88C005EC162 /* ObservableType.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE4DF3F1DA3A88C005EC162 /* ObservableType.swift */; }; BBE4DF451DA3A908005EC162 /* ValueChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE4DF441DA3A908005EC162 /* ValueChange.swift */; }; BBE4DF471DA3A908005EC162 /* ValueChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE4DF441DA3A908005EC162 /* ValueChange.swift */; }; BBE4DF481DA3A908005EC162 /* ValueChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE4DF441DA3A908005EC162 /* ValueChange.swift */; }; BBE4DF4C1DA409EE005EC162 /* DistinctUnion.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE4DF491DA409EE005EC162 /* DistinctUnion.swift */; }; BBE4DF4D1DA409EE005EC162 /* DistinctUnion.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE4DF491DA409EE005EC162 /* DistinctUnion.swift */; }; BBE4DF551DA41373005EC162 /* DistinctUnionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE4DF4E1DA412A0005EC162 /* DistinctUnionTests.swift */; }; BBE4DF591DA55384005EC162 /* SetMappingForValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE4DF561DA55384005EC162 /* SetMappingForValue.swift */; }; BBE4DF5A1DA55384005EC162 /* SetMappingForValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE4DF561DA55384005EC162 /* SetMappingForValue.swift */; }; BBE4DF5E1DA55DA3005EC162 /* SetMappingBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE4DF5B1DA55DA3005EC162 /* SetMappingBase.swift */; }; BBE4DF5F1DA55DA3005EC162 /* SetMappingBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE4DF5B1DA55DA3005EC162 /* SetMappingBase.swift */; }; BBE4DF661DA5656B005EC162 /* SetMappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE4DF611DA5654F005EC162 /* SetMappingTests.swift */; }; BBE4DF681DA5656D005EC162 /* SetMappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE4DF611DA5654F005EC162 /* SetMappingTests.swift */; }; BBE9AACA1DC0F0A5000B3DD0 /* Update.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE9AAC91DC0F0A5000B3DD0 /* Update.swift */; }; BBE9AACC1DC0F0A5000B3DD0 /* Update.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE9AAC91DC0F0A5000B3DD0 /* Update.swift */; }; BBE9AACD1DC0F0A5000B3DD0 /* Update.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE9AAC91DC0F0A5000B3DD0 /* Update.swift */; }; BBE9AACF1DC0F1F6000B3DD0 /* ChangeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE9AACE1DC0F1F6000B3DD0 /* ChangeTests.swift */; }; BBE9AAD01DC0F1F6000B3DD0 /* ChangeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE9AACE1DC0F1F6000B3DD0 /* ChangeTests.swift */; }; BBE9AAD11DC0F1F6000B3DD0 /* ChangeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE9AACE1DC0F1F6000B3DD0 /* ChangeTests.swift */; }; BBE9AAD41DC0F474000B3DD0 /* BufferedSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE9AAD31DC0F474000B3DD0 /* BufferedSourceTests.swift */; }; BBE9AAD51DC0F474000B3DD0 /* BufferedSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE9AAD31DC0F474000B3DD0 /* BufferedSourceTests.swift */; }; BBE9AAD61DC0F474000B3DD0 /* BufferedSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE9AAD31DC0F474000B3DD0 /* BufferedSourceTests.swift */; }; BBE9AAD81DC0FC17000B3DD0 /* BracketingSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE9AAD71DC0FC17000B3DD0 /* BracketingSourceTests.swift */; }; BBE9AAD91DC0FC17000B3DD0 /* BracketingSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE9AAD71DC0FC17000B3DD0 /* BracketingSourceTests.swift */; }; BBE9AADA1DC0FC17000B3DD0 /* BracketingSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE9AAD71DC0FC17000B3DD0 /* BracketingSourceTests.swift */; }; BBEA2A291DBF9ADA00CCCB08 /* MockSink.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E3D1DABC81A00E85FB9 /* MockSink.swift */; }; BBEA2A2B1DBF9E9C00CCCB08 /* AnySinkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBEA2A2A1DBF9E9C00CCCB08 /* AnySinkTests.swift */; }; BBEA2A2C1DBF9E9C00CCCB08 /* AnySinkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBEA2A2A1DBF9E9C00CCCB08 /* AnySinkTests.swift */; }; BBEA2A2D1DBF9E9C00CCCB08 /* AnySinkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBEA2A2A1DBF9E9C00CCCB08 /* AnySinkTests.swift */; }; BBEA2A321DBFC3BB00CCCB08 /* SignalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55BA11C8FCE2C0050DDA9 /* SignalTests.swift */; }; BBEA2A341DBFC4C300CCCB08 /* AnySourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBEA2A331DBFC4C300CCCB08 /* AnySourceTests.swift */; }; BBEA2A351DBFC4C300CCCB08 /* AnySourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBEA2A331DBFC4C300CCCB08 /* AnySourceTests.swift */; }; BBEA2A361DBFC4C300CCCB08 /* AnySourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBEA2A331DBFC4C300CCCB08 /* AnySourceTests.swift */; }; BBEA2A371DBFC71000CCCB08 /* SimpleSourcesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55BA21C8FCE2C0050DDA9 /* SimpleSourcesTests.swift */; }; BBEA2A381DBFC7B800CCCB08 /* MergedSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B9B1C8FCE2C0050DDA9 /* MergedSourceTests.swift */; }; BBEA2A3A1DBFE37A00CCCB08 /* BracketingSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBEA2A391DBFE37A00CCCB08 /* BracketingSource.swift */; }; BBEA2A3B1DBFE37B00CCCB08 /* BracketingSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBEA2A391DBFE37A00CCCB08 /* BracketingSource.swift */; }; BBEA2A3C1DBFE37B00CCCB08 /* BracketingSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBEA2A391DBFE37A00CCCB08 /* BracketingSource.swift */; }; BBEA2A3D1DBFE37B00CCCB08 /* BracketingSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBEA2A391DBFE37A00CCCB08 /* BracketingSource.swift */; }; BBEA2A3F1DBFE38F00CCCB08 /* BufferedSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBEA2A3E1DBFE38F00CCCB08 /* BufferedSource.swift */; }; BBEA2A401DBFE38F00CCCB08 /* BufferedSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBEA2A3E1DBFE38F00CCCB08 /* BufferedSource.swift */; }; BBEA2A411DBFE38F00CCCB08 /* BufferedSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBEA2A3E1DBFE38F00CCCB08 /* BufferedSource.swift */; }; BBEA2A421DBFE38F00CCCB08 /* BufferedSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBEA2A3E1DBFE38F00CCCB08 /* BufferedSource.swift */; }; BBEA2A4D1DC0BB4D00CCCB08 /* SourceOperatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55BA31C8FCE2C0050DDA9 /* SourceOperatorTests.swift */; }; BBEBFF721DB77D77008AC632 /* MockArrayObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB568AF61DA6A9EE00BC4B58 /* MockArrayObserver.swift */; }; BBEBFF731DB77D77008AC632 /* ArrayModificationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBA033651DA7CFFE00E83AC1 /* ArrayModificationTests.swift */; }; BBEBFF741DB77D77008AC632 /* ArrayChangeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBA033691DA7D03300E83AC1 /* ArrayChangeTests.swift */; }; BBEBFF751DB77D77008AC632 /* ArrayChangeSeparationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBC36AE61DA83E0E00AB3E9D /* ArrayChangeSeparationTests.swift */; }; BBEBFF761DB77D77008AC632 /* ObservableArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E451DABEA5500E85FB9 /* ObservableArrayTests.swift */; }; BBEBFF771DB77D77008AC632 /* ArrayVariableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B981C8FCE2C0050DDA9 /* ArrayVariableTests.swift */; }; BBEBFF781DB77D77008AC632 /* ArrayMappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB55B9F1C8FCE2C0050DDA9 /* ArrayMappingTests.swift */; }; BBEBFF791DB77D77008AC632 /* ArrayFilteringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBC4AE4D1D9A829500FF7DE0 /* ArrayFilteringTests.swift */; }; BBEBFF7A1DB77D77008AC632 /* ArrayFoldingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9DFD1DAA8F7800E85FB9 /* ArrayFoldingTests.swift */; }; BBEBFF7B1DB77D77008AC632 /* DistinctUnionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE4DF4E1DA412A0005EC162 /* DistinctUnionTests.swift */; }; BBEBFF7C1DB77D77008AC632 /* ArrayConcatenationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCD9E491DABFA2900E85FB9 /* ArrayConcatenationTests.swift */; }; BBF3EB0E1D99ACDE006AC7CD /* RefList.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBF3EB0D1D99ACDE006AC7CD /* RefList.swift */; }; BBF3EB0F1D99ACDE006AC7CD /* RefList.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBF3EB0D1D99ACDE006AC7CD /* RefList.swift */; }; BBF3EB101D99ACDE006AC7CD /* RefList.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBF3EB0D1D99ACDE006AC7CD /* RefList.swift */; }; BBF3EB111D99ACDE006AC7CD /* RefList.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBF3EB0D1D99ACDE006AC7CD /* RefList.swift */; }; BBF400661DA108E900DA0B2C /* ObservableContains.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBF400631DA108E900DA0B2C /* ObservableContains.swift */; }; BBF400671DA108E900DA0B2C /* ObservableContains.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBF400631DA108E900DA0B2C /* ObservableContains.swift */; }; BBF7B9521EACE69B00073DF8 /* UILabel Glue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBF7B9511EACE69B00073DF8 /* UILabel Glue.swift */; }; BBF7B9541EACEB0100073DF8 /* AccumulatedSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBF7B9531EACEB0100073DF8 /* AccumulatedSource.swift */; }; BBF7B9551EACEB0100073DF8 /* AccumulatedSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBF7B9531EACEB0100073DF8 /* AccumulatedSource.swift */; }; BBF7B9561EACEB0100073DF8 /* AccumulatedSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBF7B9531EACEB0100073DF8 /* AccumulatedSource.swift */; }; BBF7B9571EACEB0100073DF8 /* AccumulatedSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBF7B9531EACEB0100073DF8 /* AccumulatedSource.swift */; }; BBF7B9591EACFC3B00073DF8 /* DependentValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBF7B9581EACFC3B00073DF8 /* DependentValue.swift */; }; BBF7B95A1EACFC3B00073DF8 /* DependentValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBF7B9581EACFC3B00073DF8 /* DependentValue.swift */; }; BBF7B95B1EACFC3B00073DF8 /* DependentValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBF7B9581EACFC3B00073DF8 /* DependentValue.swift */; }; BBF7B95C1EACFC3B00073DF8 /* DependentValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBF7B9581EACFC3B00073DF8 /* DependentValue.swift */; }; BBF7B9601EAEA60900073DF8 /* ArrayBasedTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBF7B95F1EAEA60900073DF8 /* ArrayBasedTableViewDataSource.swift */; }; BBF7B9611EAEA60900073DF8 /* ArrayBasedTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBF7B95F1EAEA60900073DF8 /* ArrayBasedTableViewDataSource.swift */; }; BBF7B9621EAEA60900073DF8 /* ArrayBasedTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBF7B95F1EAEA60900073DF8 /* ArrayBasedTableViewDataSource.swift */; }; BBF7B9631EAEA60900073DF8 /* ArrayBasedTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBF7B95F1EAEA60900073DF8 /* ArrayBasedTableViewDataSource.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ BB351B021DB81E67005F083F /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BBB55AA91C8F80020050DDA9 /* Project object */; proxyType = 1; remoteGlobalIDString = BBB55AD31C8F88F20050DDA9; remoteInfo = macOS; }; BBB55ABE1C8F80020050DDA9 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BBB55AA91C8F80020050DDA9 /* Project object */; proxyType = 1; remoteGlobalIDString = BBB55AB11C8F80020050DDA9; remoteInfo = GlueKit; }; BBB55ADF1C8F88F20050DDA9 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BBB55AA91C8F80020050DDA9 /* Project object */; proxyType = 1; remoteGlobalIDString = BBB55AD31C8F88F20050DDA9; remoteInfo = GlueKit; }; BBB55B091C8F8CBC0050DDA9 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BBB55AA91C8F80020050DDA9 /* Project object */; proxyType = 1; remoteGlobalIDString = BBB55AFD1C8F8CBB0050DDA9; remoteInfo = GlueKit; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ BB015F4E1DC624FB00C8C05A /* Language Enhancements.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; lineEnding = 0; path = "Language Enhancements.md"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.markdown; }; BB1657E61D71D65900094BAC /* ArrayChangeSeparation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayChangeSeparation.swift; sourceTree = ""; }; BB170F231DC1066D0000443E /* UpdateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateTests.swift; sourceTree = ""; }; BB170F271DC106DE0000443E /* TestChange.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestChange.swift; sourceTree = ""; }; BB170F2B1DC10D350000443E /* TestObservable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestObservable.swift; sourceTree = ""; }; BB170F2F1DC10DDB0000443E /* ChangesSourceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChangesSourceTests.swift; sourceTree = ""; }; BB170F331DC110BD0000443E /* TestUpdatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestUpdatable.swift; sourceTree = ""; }; BB170F371DC1545E0000443E /* TransactionStateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionStateTests.swift; sourceTree = ""; }; BB170F3D1DC221180000443E /* ValueChangeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueChangeTests.swift; sourceTree = ""; }; BB170F411DC22A300000443E /* UpdatableValueTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = UpdatableValueTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; BB1E4D691D61EA5200F1831F /* SetSortingByMappingToComparable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetSortingByMappingToComparable.swift; sourceTree = ""; }; BB318F6A1CB9922F0086EE83 /* UIBarButtonItem Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = "UIBarButtonItem Extensions.swift"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; BB351AFC1DB81E67005F083F /* PerformanceTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PerformanceTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; BB3982EC1EC109C4000CDCB5 /* UISearchBar Glue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UISearchBar Glue.swift"; sourceTree = ""; }; BB3D12AF1E49FBF500097510 /* SipHash.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SipHash.framework; path = "../../Library/Developer/Xcode/DerivedData/GlueKit-gstdcmlxjxkcnggtlmasijktewlg/Build/Products/Debug/SipHash.framework"; sourceTree = ""; }; BB3D12B11E49FC5A00097510 /* BTree.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = BTree.framework; path = Carthage/Build/iOS/BTree.framework; sourceTree = ""; }; BB3D12B31E49FC5E00097510 /* SipHash.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SipHash.framework; path = Carthage/Build/iOS/SipHash.framework; sourceTree = ""; }; BB3D12B51E49FC7600097510 /* BTree.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = BTree.framework; path = Carthage/Build/Mac/BTree.framework; sourceTree = ""; }; BB3D12B61E49FC7600097510 /* SipHash.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SipHash.framework; path = Carthage/Build/Mac/SipHash.framework; sourceTree = ""; }; BB3D12B91E49FC8000097510 /* BTree.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = BTree.framework; path = Carthage/Build/watchOS/BTree.framework; sourceTree = ""; }; BB3D12BA1E49FC8000097510 /* SipHash.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SipHash.framework; path = Carthage/Build/watchOS/SipHash.framework; sourceTree = ""; }; BB3D12BD1E49FC8700097510 /* BTree.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = BTree.framework; path = Carthage/Build/tvOS/BTree.framework; sourceTree = ""; }; BB3D12BE1E49FC8700097510 /* SipHash.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SipHash.framework; path = Carthage/Build/tvOS/SipHash.framework; sourceTree = ""; }; BB3D92951D647E83003BEDBF /* SetReference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetReference.swift; sourceTree = ""; }; BB3D929A1D64862D003BEDBF /* ArrayReference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayReference.swift; sourceTree = ""; }; BB4052011C958712003D8F5B /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.3.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; BB471D991DA57FF8002550B0 /* SetSortingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = SetSortingTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; BB471D9E1DA58EA3002550B0 /* .codecov.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .codecov.yml; sourceTree = ""; }; BB568AE81DA66E6200BC4B58 /* Cartfile */ = {isa = PBXFileReference; lastKnownFileType = text; path = Cartfile; sourceTree = ""; }; BB568AEE1DA693F700BC4B58 /* SetFilteringTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetFilteringTests.swift; sourceTree = ""; }; BB568AF21DA6942700BC4B58 /* MockSetObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockSetObserver.swift; sourceTree = ""; }; BB568AF61DA6A9EE00BC4B58 /* MockArrayObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockArrayObserver.swift; sourceTree = ""; }; BB5B02DA1F5E3FC50084D86B /* ArrayGatheringSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayGatheringSource.swift; sourceTree = ""; }; BB5B02DF1F5E3FDE0084D86B /* SetGatheringSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetGatheringSource.swift; sourceTree = ""; }; BB6139821F5E1B3F005455D5 /* NSTextField Glue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTextField Glue.swift"; sourceTree = ""; }; BB67E5511C9B6A63002B0AB5 /* CADisplayLink Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CADisplayLink Extensions.swift"; sourceTree = ""; }; BB69A7EE1D6AFF1A001D2821 /* Abstract.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Abstract.swift; sourceTree = ""; }; BB69A7FF1D6B2377001D2821 /* BufferedArray.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BufferedArray.swift; sourceTree = ""; }; BB69A83A1D6B90F5001D2821 /* ArrayConcatenation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayConcatenation.swift; sourceTree = ""; }; BB69A83F1D6B9A7E001D2821 /* ArrayMappingForValue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayMappingForValue.swift; sourceTree = ""; }; BB6CE3B31DC4C21C00295C55 /* TransformedSink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransformedSink.swift; sourceTree = ""; }; BB7E04C71DA7233B00BE3051 /* ArrayMappingForArrayField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayMappingForArrayField.swift; sourceTree = ""; }; BB7E04CC1DA723F300BE3051 /* SetMappingForValueField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetMappingForValueField.swift; sourceTree = ""; }; BB7E04D11DA7240C00BE3051 /* SetMappingForSequence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetMappingForSequence.swift; sourceTree = ""; }; BB7E04D61DA7242F00BE3051 /* SetMappingForSetField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetMappingForSetField.swift; sourceTree = ""; }; BB7E04DB1DA7244A00BE3051 /* SetMappingForArrayField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetMappingForArrayField.swift; sourceTree = ""; }; BB7E04E01DA7255D00BE3051 /* ArrayFilteringIndexmap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayFilteringIndexmap.swift; sourceTree = ""; }; BB7E04E51DA725A500BE3051 /* ArrayFilteringOnObservableBool.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayFilteringOnObservableBool.swift; sourceTree = ""; }; BB82E3E01D98F34E0035DDAD /* ArrayFilteringOnPredicate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayFilteringOnPredicate.swift; sourceTree = ""; }; BB88C40D1DC3A08B00C7EC3C /* ObservableTypeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ObservableTypeTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; BB88C4121DC3B65F00C7EC3C /* ValueBufferingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ValueBufferingTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; BB8990021D5DF2D20097F350 /* SetChange.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetChange.swift; sourceTree = ""; }; BB8990041D5DF8630097F350 /* Change.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Change.swift; sourceTree = ""; }; BB8990091D5DF88C0097F350 /* ObservableSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ObservableSet.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; BB89901A1D5F6AAE0097F350 /* UpdatableSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdatableSet.swift; sourceTree = ""; }; BB89901F1D5F6F980097F350 /* SetVariable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetVariable.swift; sourceTree = ""; }; BB8990251D5F84050097F350 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; BB8990281D5F84210097F350 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS3.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; BB89902A1D5F842C0097F350 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; BB89902C1D5F84340097F350 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; BB8D366D1DA116CB000D44C5 /* Bookshelf.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bookshelf.swift; sourceTree = ""; }; BB8D36711DA14800000D44C5 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; BB948BBB1DD5EFC900B0734C /* ComputedUpdatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ComputedUpdatable.swift; sourceTree = ""; }; BB948BC01DD720FE00B0734C /* UISwitch Glue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UISwitch Glue.swift"; sourceTree = ""; }; BB9D73C81DBB2EB400E20D67 /* Connect.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Connect.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; BB9D73CE1DBB38F900E20D67 /* Sink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Sink.swift; sourceTree = ""; }; BB9D73DF1DBB61C100E20D67 /* TransactionalThing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionalThing.swift; sourceTree = ""; }; BB9D73E51DBB727300E20D67 /* ChangesSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ChangesSource.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; BB9D73EE1DBDFEE400E20D67 /* TwoWayBinding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TwoWayBinding.swift; sourceTree = ""; }; BB9D73F31DBE03EB00E20D67 /* DispatchSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DispatchSource.swift; sourceTree = ""; }; BB9D73FD1DBE061D00E20D67 /* OwnedSink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OwnedSink.swift; sourceTree = ""; }; BB9FCDCB1F5E54BF001B8781 /* NSButton Glue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSButton Glue.swift"; sourceTree = ""; }; BB9FCDCD1F5EB646001B8781 /* NSControl Glue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSControl Glue.swift"; sourceTree = ""; }; BB9FCDCF1F5ECFF3001B8781 /* NSPopUpButton Glue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSPopUpButton Glue.swift"; sourceTree = ""; }; BBA033561DA726AA00E83AC1 /* SetFilteringOnObservableBool.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetFilteringOnObservableBool.swift; sourceTree = ""; }; BBA0335B1DA7276200E83AC1 /* SetSortingByComparator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetSortingByComparator.swift; sourceTree = ""; }; BBA033601DA7278C00E83AC1 /* SetSortingByMappingToObservableComparable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetSortingByMappingToObservableComparable.swift; sourceTree = ""; }; BBA033651DA7CFFE00E83AC1 /* ArrayModificationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayModificationTests.swift; sourceTree = ""; }; BBA033691DA7D03300E83AC1 /* ArrayChangeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayChangeTests.swift; sourceTree = ""; }; BBAA827C1C91D43700586903 /* Type Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Type Helpers.swift"; sourceTree = ""; }; BBB4F2511C9594A9002532CC /* UIDevice Glue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIDevice Glue.swift"; sourceTree = ""; }; BBB4F26E1C99E6CB002532CC /* UIGestureRecognizer Glue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIGestureRecognizer Glue.swift"; sourceTree = ""; }; BBB55AB21C8F80020050DDA9 /* GlueKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GlueKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; BBB55AB71C8F80020050DDA9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; BBB55ABC1C8F80020050DDA9 /* GlueKit-Test.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "GlueKit-Test.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; BBB55AC31C8F80020050DDA9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; BBB55ACC1C8F80660050DDA9 /* version.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = version.xcconfig; sourceTree = ""; }; BBB55AD41C8F88F20050DDA9 /* GlueKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GlueKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; BBB55ADD1C8F88F20050DDA9 /* GlueKit-Test.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "GlueKit-Test.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; BBB55AF11C8F8BE00050DDA9 /* GlueKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GlueKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; BBB55AFE1C8F8CBB0050DDA9 /* GlueKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GlueKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; BBB55B071C8F8CBB0050DDA9 /* GlueKit-Test.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "GlueKit-Test.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; BBB55B1C1C8F90F60050DDA9 /* .travis.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .travis.yml; sourceTree = ""; }; BBB55B1D1C8F9E850050DDA9 /* LICENSE.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = LICENSE.md; sourceTree = ""; }; BBB55B1E1C8F9E920050DDA9 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; lineEnding = 0; path = README.md; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.markdown; }; BBB55B1F1C8FCC810050DDA9 /* ArrayChange.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayChange.swift; sourceTree = ""; }; BBB55B201C8FCC810050DDA9 /* ArrayVariable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayVariable.swift; sourceTree = ""; }; BBB55B221C8FCC810050DDA9 /* Connector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Connector.swift; sourceTree = ""; }; BBB55B231C8FCC810050DDA9 /* SetFilteringOnPredicate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetFilteringOnPredicate.swift; sourceTree = ""; }; BBB55B251C8FCC810050DDA9 /* Locks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Locks.swift; sourceTree = ""; }; BBB55B261C8FCC810050DDA9 /* MergedSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MergedSource.swift; sourceTree = ""; }; BBB55B271C8FCC810050DDA9 /* NSNotificationCenter Support.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSNotificationCenter Support.swift"; sourceTree = ""; }; BBB55B281C8FCC810050DDA9 /* BufferedValue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = BufferedValue.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; BBB55B291C8FCC810050DDA9 /* ObservableValue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ObservableValue.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; BBB55B2A1C8FCC810050DDA9 /* ObservableArray.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObservableArray.swift; sourceTree = ""; }; BBB55B2B1C8FCC810050DDA9 /* Reference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Reference.swift; sourceTree = ""; }; BBB55B2C1C8FCC810050DDA9 /* ValueMappingForValueField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ValueMappingForValueField.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; BBB55B2D1C8FCC810050DDA9 /* ArrayMappingForValueField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayMappingForValueField.swift; sourceTree = ""; }; BBB55B2E1C8FCC810050DDA9 /* Signal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Signal.swift; sourceTree = ""; }; BBB55B2F1C8FCC810050DDA9 /* SimpleSources.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleSources.swift; sourceTree = ""; }; BBB55B301C8FCC810050DDA9 /* Source.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Source.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; BBB55B311C8FCC810050DDA9 /* TransformedSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransformedSource.swift; sourceTree = ""; }; BBB55B321C8FCC820050DDA9 /* TimerSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimerSource.swift; sourceTree = ""; }; BBB55B331C8FCC820050DDA9 /* UpdatableValue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdatableValue.swift; sourceTree = ""; }; BBB55B341C8FCC820050DDA9 /* UpdatableArray.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdatableArray.swift; sourceTree = ""; }; BBB55B351C8FCC820050DDA9 /* Variable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Variable.swift; sourceTree = ""; }; BBB55B981C8FCE2C0050DDA9 /* ArrayVariableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ArrayVariableTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; BBB55B9A1C8FCE2C0050DDA9 /* KVOSupportTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = KVOSupportTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; BBB55B9B1C8FCE2C0050DDA9 /* MergedSourceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MergedSourceTests.swift; sourceTree = ""; }; BBB55B9C1C8FCE2C0050DDA9 /* NotificationCenterSupportTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = NotificationCenterSupportTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; BBB55B9D1C8FCE2C0050DDA9 /* ObservableValueTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ObservableValueTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; BBB55B9E1C8FCE2C0050DDA9 /* DistinctTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DistinctTests.swift; sourceTree = ""; }; BBB55B9F1C8FCE2C0050DDA9 /* ArrayMappingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayMappingTests.swift; sourceTree = ""; }; BBB55BA01C8FCE2C0050DDA9 /* ValueMappingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ValueMappingTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; BBB55BA11C8FCE2C0050DDA9 /* SignalTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = SignalTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; BBB55BA21C8FCE2C0050DDA9 /* SimpleSourcesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleSourcesTests.swift; sourceTree = ""; }; BBB55BA31C8FCE2C0050DDA9 /* SourceOperatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = SourceOperatorTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; BBB55BA41C8FCE2C0050DDA9 /* TestUtilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestUtilities.swift; sourceTree = ""; }; BBB55BA51C8FCE2C0050DDA9 /* TimerSourceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = TimerSourceTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; BBB55BA61C8FCE2C0050DDA9 /* TwoWayBindingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = TwoWayBindingTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; BBB55BA71C8FCE2C0050DDA9 /* VariableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = VariableTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; BBB55BDC1C8FCFAD0050DDA9 /* Overview.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; lineEnding = 0; path = Overview.md; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.markdown; }; BBB55BDD1C8FD0160050DDA9 /* Demo.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = Demo.playground; path = ../GlueKit/Demo.playground; sourceTree = ""; }; BBB55BE61C8FD1C60050DDA9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; BBB55BEA1C8FD25F0050DDA9 /* GlueKitPerformanceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = GlueKitPerformanceTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; BBBBEC8C1DCA1AEB000B646D /* ValueReference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueReference.swift; sourceTree = ""; }; BBBBEC911DCA1D43000B646D /* ValueReferenceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueReferenceTests.swift; sourceTree = ""; }; BBBBEC951DCA1FEB000B646D /* ArrayReferenceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayReferenceTests.swift; sourceTree = ""; }; BBBBEC991DCA223A000B646D /* SetReferenceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetReferenceTests.swift; sourceTree = ""; }; BBBBEC9D1DCA2FAB000B646D /* NSUserDefaultsSupportTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSUserDefaultsSupportTests.swift; sourceTree = ""; }; BBBBECA11DCA3AA8000B646D /* DispatchSourceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = DispatchSourceTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; BBBBECA51DCA3FF0000B646D /* BufferedSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BufferedSet.swift; sourceTree = ""; }; BBBBECAA1DCA4168000B646D /* ArrayBufferingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayBufferingTests.swift; sourceTree = ""; }; BBBBECAE1DCA446A000B646D /* SetBufferingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetBufferingTests.swift; sourceTree = ""; }; BBBFD14A1EBE673200EC9814 /* SetSortingByComparableField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetSortingByComparableField.swift; sourceTree = ""; }; BBC1F1D01D7EDF4A00A320F5 /* GlueKit.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; path = GlueKit.podspec; sourceTree = ""; }; BBC2A8281CBC14C600394D24 /* NSObject Glue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSObject Glue.swift"; sourceTree = ""; }; BBC36AE61DA83E0E00AB3E9D /* ArrayChangeSeparationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayChangeSeparationTests.swift; sourceTree = ""; }; BBC3C84D1C93164200E10D59 /* UIControl Glue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIControl Glue.swift"; sourceTree = ""; }; BBC4AE4D1D9A829500FF7DE0 /* ArrayFilteringTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayFilteringTests.swift; sourceTree = ""; }; BBCD9DF41DA9104A00E85FB9 /* RefListTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RefListTests.swift; sourceTree = ""; }; BBCD9DF81DAA885E00E85FB9 /* ArrayFolding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ArrayFolding.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; BBCD9DFD1DAA8F7800E85FB9 /* ArrayFoldingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayFoldingTests.swift; sourceTree = ""; }; BBCD9E011DAA910500E85FB9 /* SetFolding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetFolding.swift; sourceTree = ""; }; BBCD9E061DAA918400E85FB9 /* SetFoldingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetFoldingTests.swift; sourceTree = ""; }; BBCD9E0A1DAAB6E700E85FB9 /* MockUpdateSink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockUpdateSink.swift; sourceTree = ""; }; BBCD9E0E1DAAC33700E85FB9 /* ValueMappingForValue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueMappingForValue.swift; sourceTree = ""; }; BBCD9E131DAAC4DC00E85FB9 /* ValueMappingForArrayField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueMappingForArrayField.swift; sourceTree = ""; }; BBCD9E181DAAC50900E85FB9 /* ValueMappingForSourceField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ValueMappingForSourceField.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; BBCD9E1D1DAAC7E600E85FB9 /* ValueMappingForSetField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ValueMappingForSetField.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; BBCD9E221DAAD26200E85FB9 /* CompositeObservable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompositeObservable.swift; sourceTree = ""; }; BBCD9E271DAAD2B900E85FB9 /* DistinctValue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DistinctValue.swift; sourceTree = ""; }; BBCD9E2C1DAAD4BC00E85FB9 /* CompositeUpdatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompositeUpdatable.swift; sourceTree = ""; }; BBCD9E311DAAD66500E85FB9 /* CombinedObservableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = CombinedObservableTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; BBCD9E351DAAD74600E85FB9 /* CombinedUpdatableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = CombinedUpdatableTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; BBCD9E391DABC45000E85FB9 /* TypeHelperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypeHelperTests.swift; sourceTree = ""; }; BBCD9E3D1DABC81A00E85FB9 /* MockSink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockSink.swift; sourceTree = ""; }; BBCD9E411DABD4BE00E85FB9 /* ObservableSetTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObservableSetTests.swift; sourceTree = ""; }; BBCD9E451DABEA5500E85FB9 /* ObservableArrayTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObservableArrayTests.swift; sourceTree = ""; }; BBCD9E491DABFA2900E85FB9 /* ArrayConcatenationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayConcatenationTests.swift; sourceTree = ""; }; BBCD9E4D1DABFE8800E85FB9 /* SetVariableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetVariableTests.swift; sourceTree = ""; }; BBCD9E511DAC1B1300E85FB9 /* ConnectorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ConnectorTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; BBE4DF3F1DA3A88C005EC162 /* ObservableType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ObservableType.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; BBE4DF441DA3A908005EC162 /* ValueChange.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueChange.swift; sourceTree = ""; }; BBE4DF491DA409EE005EC162 /* DistinctUnion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DistinctUnion.swift; sourceTree = ""; }; BBE4DF4E1DA412A0005EC162 /* DistinctUnionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DistinctUnionTests.swift; sourceTree = ""; }; BBE4DF561DA55384005EC162 /* SetMappingForValue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetMappingForValue.swift; sourceTree = ""; }; BBE4DF5B1DA55DA3005EC162 /* SetMappingBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetMappingBase.swift; sourceTree = ""; }; BBE4DF611DA5654F005EC162 /* SetMappingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetMappingTests.swift; sourceTree = ""; }; BBE9AAC91DC0F0A5000B3DD0 /* Update.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Update.swift; sourceTree = ""; }; BBE9AACE1DC0F1F6000B3DD0 /* ChangeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChangeTests.swift; sourceTree = ""; }; BBE9AAD31DC0F474000B3DD0 /* BufferedSourceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BufferedSourceTests.swift; sourceTree = ""; }; BBE9AAD71DC0FC17000B3DD0 /* BracketingSourceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BracketingSourceTests.swift; sourceTree = ""; }; BBEA2A2A1DBF9E9C00CCCB08 /* AnySinkTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnySinkTests.swift; sourceTree = ""; }; BBEA2A331DBFC4C300CCCB08 /* AnySourceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnySourceTests.swift; sourceTree = ""; }; BBEA2A391DBFE37A00CCCB08 /* BracketingSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BracketingSource.swift; sourceTree = ""; }; BBEA2A3E1DBFE38F00CCCB08 /* BufferedSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BufferedSource.swift; sourceTree = ""; }; BBF3EB0D1D99ACDE006AC7CD /* RefList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RefList.swift; sourceTree = ""; }; BBF400631DA108E900DA0B2C /* ObservableContains.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ObservableContains.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; BBF7B9511EACE69B00073DF8 /* UILabel Glue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UILabel Glue.swift"; sourceTree = ""; }; BBF7B9531EACEB0100073DF8 /* AccumulatedSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccumulatedSource.swift; sourceTree = ""; }; BBF7B9581EACFC3B00073DF8 /* DependentValue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DependentValue.swift; sourceTree = ""; }; BBF7B95F1EAEA60900073DF8 /* ArrayBasedTableViewDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayBasedTableViewDataSource.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ BB351AF91DB81E67005F083F /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( BB351B011DB81E67005F083F /* GlueKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; BBB55AAE1C8F80020050DDA9 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( BB89902B1D5F842C0097F350 /* Foundation.framework in Frameworks */, BB4052021C958712003D8F5B /* UIKit.framework in Frameworks */, BB3D12B21E49FC5A00097510 /* BTree.framework in Frameworks */, BB3D12B41E49FC5E00097510 /* SipHash.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; BBB55AB91C8F80020050DDA9 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( BB23E63D1C90DEB6005EFD0A /* GlueKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; BBB55AD01C8F88F20050DDA9 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( BB8990271D5F84150097F350 /* Foundation.framework in Frameworks */, BB3D12B71E49FC7600097510 /* BTree.framework in Frameworks */, BB3D12B81E49FC7600097510 /* SipHash.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; BBB55ADA1C8F88F20050DDA9 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( BB3D12C11E49FD1700097510 /* GlueKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; BBB55AED1C8F8BE00050DDA9 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( BB8990291D5F84210097F350 /* Foundation.framework in Frameworks */, BB3D12BB1E49FC8000097510 /* BTree.framework in Frameworks */, BB3D12BC1E49FC8000097510 /* SipHash.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; BBB55AFA1C8F8CBB0050DDA9 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( BB89902D1D5F84340097F350 /* Foundation.framework in Frameworks */, BB3D12BF1E49FC8700097510 /* BTree.framework in Frameworks */, BB3D12C01E49FC8700097510 /* SipHash.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; BBB55B041C8F8CBB0050DDA9 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( BBB55B081C8F8CBC0050DDA9 /* GlueKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ BB3D12AE1E49FBF500097510 /* Frameworks */ = { isa = PBXGroup; children = ( BB3D12BD1E49FC8700097510 /* BTree.framework */, BB3D12BE1E49FC8700097510 /* SipHash.framework */, BB3D12B91E49FC8000097510 /* BTree.framework */, BB3D12BA1E49FC8000097510 /* SipHash.framework */, BB3D12B51E49FC7600097510 /* BTree.framework */, BB3D12B61E49FC7600097510 /* SipHash.framework */, BB3D12B31E49FC5E00097510 /* SipHash.framework */, BB3D12B11E49FC5A00097510 /* BTree.framework */, BB4052011C958712003D8F5B /* UIKit.framework */, BB89902C1D5F84340097F350 /* Foundation.framework */, BB89902A1D5F842C0097F350 /* Foundation.framework */, BB8990281D5F84210097F350 /* Foundation.framework */, BB8990251D5F84050097F350 /* Foundation.framework */, BB3D12AF1E49FBF500097510 /* SipHash.framework */, ); name = Frameworks; sourceTree = ""; }; BB5B02D71F5E1B880084D86B /* AppKit Extensions */ = { isa = PBXGroup; children = ( BB9FCDCD1F5EB646001B8781 /* NSControl Glue.swift */, BB9FCDCB1F5E54BF001B8781 /* NSButton Glue.swift */, BB9FCDCF1F5ECFF3001B8781 /* NSPopUpButton Glue.swift */, BB6139821F5E1B3F005455D5 /* NSTextField Glue.swift */, ); name = "AppKit Extensions"; sourceTree = ""; }; BB8990011D5DF2BD0097F350 /* Observable Sets */ = { isa = PBXGroup; children = ( BB8990021D5DF2D20097F350 /* SetChange.swift */, BB8990091D5DF88C0097F350 /* ObservableSet.swift */, BB89901A1D5F6AAE0097F350 /* UpdatableSet.swift */, BB89901F1D5F6F980097F350 /* SetVariable.swift */, BBBBECA51DCA3FF0000B646D /* BufferedSet.swift */, BBF400631DA108E900DA0B2C /* ObservableContains.swift */, BBE4DF5B1DA55DA3005EC162 /* SetMappingBase.swift */, BBE4DF561DA55384005EC162 /* SetMappingForValue.swift */, BB7E04D11DA7240C00BE3051 /* SetMappingForSequence.swift */, BB7E04CC1DA723F300BE3051 /* SetMappingForValueField.swift */, BB7E04D61DA7242F00BE3051 /* SetMappingForSetField.swift */, BB7E04DB1DA7244A00BE3051 /* SetMappingForArrayField.swift */, BBB55B231C8FCC810050DDA9 /* SetFilteringOnPredicate.swift */, BBA033561DA726AA00E83AC1 /* SetFilteringOnObservableBool.swift */, BBA0335B1DA7276200E83AC1 /* SetSortingByComparator.swift */, BB1E4D691D61EA5200F1831F /* SetSortingByMappingToComparable.swift */, BBA033601DA7278C00E83AC1 /* SetSortingByMappingToObservableComparable.swift */, BBBFD14A1EBE673200EC9814 /* SetSortingByComparableField.swift */, BBCD9E011DAA910500E85FB9 /* SetFolding.swift */, BB3D92951D647E83003BEDBF /* SetReference.swift */, ); name = "Observable Sets"; sourceTree = ""; }; BB8D366C1DA116AA000D44C5 /* Examples */ = { isa = PBXGroup; children = ( BB8D366D1DA116CB000D44C5 /* Bookshelf.swift */, ); name = Examples; sourceTree = ""; }; BBB4F2531C9594E0002532CC /* Foundation Extensions */ = { isa = PBXGroup; children = ( BB9D73F31DBE03EB00E20D67 /* DispatchSource.swift */, BBB55B321C8FCC820050DDA9 /* TimerSource.swift */, BBC2A8281CBC14C600394D24 /* NSObject Glue.swift */, BBAA827C1C91D43700586903 /* Type Helpers.swift */, BBB55B271C8FCC810050DDA9 /* NSNotificationCenter Support.swift */, BBF7B9581EACFC3B00073DF8 /* DependentValue.swift */, ); name = "Foundation Extensions"; sourceTree = ""; }; BBB55AA81C8F80020050DDA9 = { isa = PBXGroup; children = ( BBB55B1E1C8F9E920050DDA9 /* README.md */, BBB55B1D1C8F9E850050DDA9 /* LICENSE.md */, BBB55B1C1C8F90F60050DDA9 /* .travis.yml */, BB471D9E1DA58EA3002550B0 /* .codecov.yml */, BBC1F1D01D7EDF4A00A320F5 /* GlueKit.podspec */, BB568AE81DA66E6200BC4B58 /* Cartfile */, BB8D36711DA14800000D44C5 /* Package.swift */, BBB55ACC1C8F80660050DDA9 /* version.xcconfig */, BBB55BDD1C8FD0160050DDA9 /* Demo.playground */, BBB55BDB1C8FCFAD0050DDA9 /* Documentation */, BBB55AB41C8F80020050DDA9 /* Sources */, BBB55AC01C8F80020050DDA9 /* Tests */, BBB55BE31C8FD1C60050DDA9 /* PerformanceTests */, BBB55AB31C8F80020050DDA9 /* Products */, BB3D12AE1E49FBF500097510 /* Frameworks */, ); sourceTree = ""; }; BBB55AB31C8F80020050DDA9 /* Products */ = { isa = PBXGroup; children = ( BBB55AB21C8F80020050DDA9 /* GlueKit.framework */, BBB55ABC1C8F80020050DDA9 /* GlueKit-Test.xctest */, BBB55AD41C8F88F20050DDA9 /* GlueKit.framework */, BBB55ADD1C8F88F20050DDA9 /* GlueKit-Test.xctest */, BBB55AF11C8F8BE00050DDA9 /* GlueKit.framework */, BBB55AFE1C8F8CBB0050DDA9 /* GlueKit.framework */, BBB55B071C8F8CBB0050DDA9 /* GlueKit-Test.xctest */, BB351AFC1DB81E67005F083F /* PerformanceTests.xctest */, ); name = Products; sourceTree = ""; }; BBB55AB41C8F80020050DDA9 /* Sources */ = { isa = PBXGroup; children = ( BBB55AB71C8F80020050DDA9 /* Info.plist */, BBB55B921C8FCCD40050DDA9 /* Tools */, BBB55B931C8FCD010050DDA9 /* Sources and Signals */, BBB55B941C8FCD320050DDA9 /* Abstract Observable */, BBE4DF3E1DA3A869005EC162 /* Observable Values */, BBB55B951C8FCD4E0050DDA9 /* Observable Arrays */, BB8990011D5DF2BD0097F350 /* Observable Sets */, BBB4F2531C9594E0002532CC /* Foundation Extensions */, BB5B02D71F5E1B880084D86B /* AppKit Extensions */, BBC3C84F1C93164700E10D59 /* UIKit Extensions */, ); path = Sources; sourceTree = ""; }; BBB55AC01C8F80020050DDA9 /* Tests */ = { isa = PBXGroup; children = ( BBB55AC31C8F80020050DDA9 /* Info.plist */, BBB55BA41C8FCE2C0050DDA9 /* TestUtilities.swift */, BBCD9DF41DA9104A00E85FB9 /* RefListTests.swift */, BBB55BD81C8FCE740050DDA9 /* Sources and Signals */, BBE9AAD21DC0F204000B3DD0 /* Abstract Observables */, BBB55BD91C8FCE9D0050DDA9 /* Observable Values */, BBB55BDA1C8FCEAE0050DDA9 /* Observable Arrays */, BBE4DF601DA56530005EC162 /* Observable Sets */, BBEA2A4C1DBFE60500CCCB08 /* Foundation Extensions */, BB8D366C1DA116AA000D44C5 /* Examples */, ); name = Tests; path = Tests/GlueKitTests; sourceTree = ""; }; BBB55B921C8FCCD40050DDA9 /* Tools */ = { isa = PBXGroup; children = ( BBB55B251C8FCC810050DDA9 /* Locks.swift */, BBB55B2B1C8FCC810050DDA9 /* Reference.swift */, BB69A7EE1D6AFF1A001D2821 /* Abstract.swift */, BBF3EB0D1D99ACDE006AC7CD /* RefList.swift */, ); name = Tools; sourceTree = ""; }; BBB55B931C8FCD010050DDA9 /* Sources and Signals */ = { isa = PBXGroup; children = ( BB9D73CE1DBB38F900E20D67 /* Sink.swift */, BB6CE3B31DC4C21C00295C55 /* TransformedSink.swift */, BB9D73FD1DBE061D00E20D67 /* OwnedSink.swift */, BBB55B301C8FCC810050DDA9 /* Source.swift */, BBB55B221C8FCC810050DDA9 /* Connector.swift */, BB9D73C81DBB2EB400E20D67 /* Connect.swift */, BBB55B2E1C8FCC810050DDA9 /* Signal.swift */, BBB55B2F1C8FCC810050DDA9 /* SimpleSources.swift */, BBB55B261C8FCC810050DDA9 /* MergedSource.swift */, BB5B02DA1F5E3FC50084D86B /* ArrayGatheringSource.swift */, BB5B02DF1F5E3FDE0084D86B /* SetGatheringSource.swift */, BBB55B311C8FCC810050DDA9 /* TransformedSource.swift */, BBEA2A3E1DBFE38F00CCCB08 /* BufferedSource.swift */, BBEA2A391DBFE37A00CCCB08 /* BracketingSource.swift */, BBF7B9531EACEB0100073DF8 /* AccumulatedSource.swift */, ); name = "Sources and Signals"; sourceTree = ""; }; BBB55B941C8FCD320050DDA9 /* Abstract Observable */ = { isa = PBXGroup; children = ( BB8990041D5DF8630097F350 /* Change.swift */, BBE9AAC91DC0F0A5000B3DD0 /* Update.swift */, BBE4DF3F1DA3A88C005EC162 /* ObservableType.swift */, BB9D73E51DBB727300E20D67 /* ChangesSource.swift */, BB9D73DF1DBB61C100E20D67 /* TransactionalThing.swift */, ); name = "Abstract Observable"; sourceTree = ""; }; BBB55B951C8FCD4E0050DDA9 /* Observable Arrays */ = { isa = PBXGroup; children = ( BBB55B1F1C8FCC810050DDA9 /* ArrayChange.swift */, BB1657E61D71D65900094BAC /* ArrayChangeSeparation.swift */, BBB55B2A1C8FCC810050DDA9 /* ObservableArray.swift */, BBB55B341C8FCC820050DDA9 /* UpdatableArray.swift */, BBB55B201C8FCC810050DDA9 /* ArrayVariable.swift */, BB69A7FF1D6B2377001D2821 /* BufferedArray.swift */, BB69A83F1D6B9A7E001D2821 /* ArrayMappingForValue.swift */, BBB55B2D1C8FCC810050DDA9 /* ArrayMappingForValueField.swift */, BB7E04C71DA7233B00BE3051 /* ArrayMappingForArrayField.swift */, BB7E04E01DA7255D00BE3051 /* ArrayFilteringIndexmap.swift */, BB82E3E01D98F34E0035DDAD /* ArrayFilteringOnPredicate.swift */, BB7E04E51DA725A500BE3051 /* ArrayFilteringOnObservableBool.swift */, BBCD9DF81DAA885E00E85FB9 /* ArrayFolding.swift */, BB3D929A1D64862D003BEDBF /* ArrayReference.swift */, BB69A83A1D6B90F5001D2821 /* ArrayConcatenation.swift */, BBE4DF491DA409EE005EC162 /* DistinctUnion.swift */, ); name = "Observable Arrays"; sourceTree = ""; }; BBB55BD81C8FCE740050DDA9 /* Sources and Signals */ = { isa = PBXGroup; children = ( BBCD9E3D1DABC81A00E85FB9 /* MockSink.swift */, BBEA2A2A1DBF9E9C00CCCB08 /* AnySinkTests.swift */, BBEA2A331DBFC4C300CCCB08 /* AnySourceTests.swift */, BBB55BA11C8FCE2C0050DDA9 /* SignalTests.swift */, BBB55BA21C8FCE2C0050DDA9 /* SimpleSourcesTests.swift */, BBB55B9B1C8FCE2C0050DDA9 /* MergedSourceTests.swift */, BBB55BA31C8FCE2C0050DDA9 /* SourceOperatorTests.swift */, BBE9AAD31DC0F474000B3DD0 /* BufferedSourceTests.swift */, BBE9AAD71DC0FC17000B3DD0 /* BracketingSourceTests.swift */, BBCD9E511DAC1B1300E85FB9 /* ConnectorTests.swift */, ); name = "Sources and Signals"; sourceTree = ""; }; BBB55BD91C8FCE9D0050DDA9 /* Observable Values */ = { isa = PBXGroup; children = ( BB170F3D1DC221180000443E /* ValueChangeTests.swift */, BBB55B9D1C8FCE2C0050DDA9 /* ObservableValueTests.swift */, BB170F411DC22A300000443E /* UpdatableValueTests.swift */, BBB55BA61C8FCE2C0050DDA9 /* TwoWayBindingTests.swift */, BBB55BA71C8FCE2C0050DDA9 /* VariableTests.swift */, BBB55B9E1C8FCE2C0050DDA9 /* DistinctTests.swift */, BB88C4121DC3B65F00C7EC3C /* ValueBufferingTests.swift */, BBB55BA01C8FCE2C0050DDA9 /* ValueMappingTests.swift */, BBCD9E311DAAD66500E85FB9 /* CombinedObservableTests.swift */, BBCD9E351DAAD74600E85FB9 /* CombinedUpdatableTests.swift */, BBCD9E391DABC45000E85FB9 /* TypeHelperTests.swift */, BBBBEC911DCA1D43000B646D /* ValueReferenceTests.swift */, ); name = "Observable Values"; sourceTree = ""; }; BBB55BDA1C8FCEAE0050DDA9 /* Observable Arrays */ = { isa = PBXGroup; children = ( BB568AF61DA6A9EE00BC4B58 /* MockArrayObserver.swift */, BBA033651DA7CFFE00E83AC1 /* ArrayModificationTests.swift */, BBA033691DA7D03300E83AC1 /* ArrayChangeTests.swift */, BBC36AE61DA83E0E00AB3E9D /* ArrayChangeSeparationTests.swift */, BBCD9E451DABEA5500E85FB9 /* ObservableArrayTests.swift */, BBB55B981C8FCE2C0050DDA9 /* ArrayVariableTests.swift */, BBBBECAA1DCA4168000B646D /* ArrayBufferingTests.swift */, BBB55B9F1C8FCE2C0050DDA9 /* ArrayMappingTests.swift */, BBC4AE4D1D9A829500FF7DE0 /* ArrayFilteringTests.swift */, BBCD9DFD1DAA8F7800E85FB9 /* ArrayFoldingTests.swift */, BBE4DF4E1DA412A0005EC162 /* DistinctUnionTests.swift */, BBCD9E491DABFA2900E85FB9 /* ArrayConcatenationTests.swift */, BBBBEC951DCA1FEB000B646D /* ArrayReferenceTests.swift */, ); name = "Observable Arrays"; sourceTree = ""; }; BBB55BDB1C8FCFAD0050DDA9 /* Documentation */ = { isa = PBXGroup; children = ( BBB55BDC1C8FCFAD0050DDA9 /* Overview.md */, BB015F4E1DC624FB00C8C05A /* Language Enhancements.md */, ); path = Documentation; sourceTree = ""; }; BBB55BE31C8FD1C60050DDA9 /* PerformanceTests */ = { isa = PBXGroup; children = ( BBB55BEA1C8FD25F0050DDA9 /* GlueKitPerformanceTests.swift */, BBB55BE61C8FD1C60050DDA9 /* Info.plist */, ); name = PerformanceTests; path = Tests/PerformanceTests; sourceTree = ""; }; BBC3C84F1C93164700E10D59 /* UIKit Extensions */ = { isa = PBXGroup; children = ( BBC3C84D1C93164200E10D59 /* UIControl Glue.swift */, BB948BC01DD720FE00B0734C /* UISwitch Glue.swift */, BBF7B9511EACE69B00073DF8 /* UILabel Glue.swift */, BBB4F2511C9594A9002532CC /* UIDevice Glue.swift */, BB3982EC1EC109C4000CDCB5 /* UISearchBar Glue.swift */, BBF7B95F1EAEA60900073DF8 /* ArrayBasedTableViewDataSource.swift */, BBB4F26E1C99E6CB002532CC /* UIGestureRecognizer Glue.swift */, BB318F6A1CB9922F0086EE83 /* UIBarButtonItem Extensions.swift */, BB67E5511C9B6A63002B0AB5 /* CADisplayLink Extensions.swift */, ); name = "UIKit Extensions"; sourceTree = ""; }; BBE4DF3E1DA3A869005EC162 /* Observable Values */ = { isa = PBXGroup; children = ( BBE4DF441DA3A908005EC162 /* ValueChange.swift */, BBB55B291C8FCC810050DDA9 /* ObservableValue.swift */, BBB55B331C8FCC820050DDA9 /* UpdatableValue.swift */, BB948BBB1DD5EFC900B0734C /* ComputedUpdatable.swift */, BB9D73EE1DBDFEE400E20D67 /* TwoWayBinding.swift */, BBB55B351C8FCC820050DDA9 /* Variable.swift */, BBB55B281C8FCC810050DDA9 /* BufferedValue.swift */, BBCD9E271DAAD2B900E85FB9 /* DistinctValue.swift */, BBCD9E221DAAD26200E85FB9 /* CompositeObservable.swift */, BBCD9E2C1DAAD4BC00E85FB9 /* CompositeUpdatable.swift */, BBCD9E0E1DAAC33700E85FB9 /* ValueMappingForValue.swift */, BBCD9E181DAAC50900E85FB9 /* ValueMappingForSourceField.swift */, BBB55B2C1C8FCC810050DDA9 /* ValueMappingForValueField.swift */, BBCD9E131DAAC4DC00E85FB9 /* ValueMappingForArrayField.swift */, BBCD9E1D1DAAC7E600E85FB9 /* ValueMappingForSetField.swift */, BBBBEC8C1DCA1AEB000B646D /* ValueReference.swift */, ); name = "Observable Values"; sourceTree = ""; }; BBE4DF601DA56530005EC162 /* Observable Sets */ = { isa = PBXGroup; children = ( BB568AF21DA6942700BC4B58 /* MockSetObserver.swift */, BBCD9E411DABD4BE00E85FB9 /* ObservableSetTests.swift */, BBCD9E4D1DABFE8800E85FB9 /* SetVariableTests.swift */, BBBBECAE1DCA446A000B646D /* SetBufferingTests.swift */, BBE4DF611DA5654F005EC162 /* SetMappingTests.swift */, BB568AEE1DA693F700BC4B58 /* SetFilteringTests.swift */, BB471D991DA57FF8002550B0 /* SetSortingTests.swift */, BBCD9E061DAA918400E85FB9 /* SetFoldingTests.swift */, BBBBEC991DCA223A000B646D /* SetReferenceTests.swift */, ); name = "Observable Sets"; sourceTree = ""; }; BBE9AAD21DC0F204000B3DD0 /* Abstract Observables */ = { isa = PBXGroup; children = ( BBCD9E0A1DAAB6E700E85FB9 /* MockUpdateSink.swift */, BB170F271DC106DE0000443E /* TestChange.swift */, BB170F2B1DC10D350000443E /* TestObservable.swift */, BB170F331DC110BD0000443E /* TestUpdatable.swift */, BBE9AACE1DC0F1F6000B3DD0 /* ChangeTests.swift */, BB170F231DC1066D0000443E /* UpdateTests.swift */, BB88C40D1DC3A08B00C7EC3C /* ObservableTypeTests.swift */, BB170F2F1DC10DDB0000443E /* ChangesSourceTests.swift */, BB170F371DC1545E0000443E /* TransactionStateTests.swift */, ); name = "Abstract Observables"; sourceTree = ""; }; BBEA2A4C1DBFE60500CCCB08 /* Foundation Extensions */ = { isa = PBXGroup; children = ( BBB55BA51C8FCE2C0050DDA9 /* TimerSourceTests.swift */, BBB55B9A1C8FCE2C0050DDA9 /* KVOSupportTests.swift */, BBB55B9C1C8FCE2C0050DDA9 /* NotificationCenterSupportTests.swift */, BBBBEC9D1DCA2FAB000B646D /* NSUserDefaultsSupportTests.swift */, BBBBECA11DCA3AA8000B646D /* DispatchSourceTests.swift */, ); name = "Foundation Extensions"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ BBB55AAF1C8F80020050DDA9 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; BBB55AD11C8F88F20050DDA9 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; BBB55AEE1C8F8BE00050DDA9 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; BBB55AFB1C8F8CBB0050DDA9 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ BB351AFB1DB81E67005F083F /* PerformanceTests */ = { isa = PBXNativeTarget; buildConfigurationList = BB351B041DB81E67005F083F /* Build configuration list for PBXNativeTarget "PerformanceTests" */; buildPhases = ( BB351AF81DB81E67005F083F /* Sources */, BB351AF91DB81E67005F083F /* Frameworks */, BB351AFA1DB81E67005F083F /* Resources */, ); buildRules = ( ); dependencies = ( BB351B031DB81E67005F083F /* PBXTargetDependency */, ); name = PerformanceTests; productName = PerformanceTests; productReference = BB351AFC1DB81E67005F083F /* PerformanceTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; BBB55AB11C8F80020050DDA9 /* iOS */ = { isa = PBXNativeTarget; buildConfigurationList = BBB55AC61C8F80020050DDA9 /* Build configuration list for PBXNativeTarget "iOS" */; buildPhases = ( BBB55AAD1C8F80020050DDA9 /* Sources */, BBB55AAE1C8F80020050DDA9 /* Frameworks */, BBB55AAF1C8F80020050DDA9 /* Headers */, BBB55AB01C8F80020050DDA9 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = iOS; productName = GlueKit; productReference = BBB55AB21C8F80020050DDA9 /* GlueKit.framework */; productType = "com.apple.product-type.framework"; }; BBB55ABB1C8F80020050DDA9 /* iOS Tests */ = { isa = PBXNativeTarget; buildConfigurationList = BBB55AC91C8F80020050DDA9 /* Build configuration list for PBXNativeTarget "iOS Tests" */; buildPhases = ( BBB55AB81C8F80020050DDA9 /* Sources */, BBB55AB91C8F80020050DDA9 /* Frameworks */, BBB55ABA1C8F80020050DDA9 /* Resources */, ); buildRules = ( ); dependencies = ( BBB55ABF1C8F80020050DDA9 /* PBXTargetDependency */, ); name = "iOS Tests"; productName = GlueKitTests; productReference = BBB55ABC1C8F80020050DDA9 /* GlueKit-Test.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; BBB55AD31C8F88F20050DDA9 /* macOS */ = { isa = PBXNativeTarget; buildConfigurationList = BBB55AE51C8F88F20050DDA9 /* Build configuration list for PBXNativeTarget "macOS" */; buildPhases = ( BBB55ACF1C8F88F20050DDA9 /* Sources */, BBB55AD01C8F88F20050DDA9 /* Frameworks */, BBB55AD11C8F88F20050DDA9 /* Headers */, BBB55AD21C8F88F20050DDA9 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = macOS; productName = GlueKit; productReference = BBB55AD41C8F88F20050DDA9 /* GlueKit.framework */; productType = "com.apple.product-type.framework"; }; BBB55ADC1C8F88F20050DDA9 /* macOS Tests */ = { isa = PBXNativeTarget; buildConfigurationList = BBB55AE81C8F88F20050DDA9 /* Build configuration list for PBXNativeTarget "macOS Tests" */; buildPhases = ( BBB55AD91C8F88F20050DDA9 /* Sources */, BBB55ADA1C8F88F20050DDA9 /* Frameworks */, BBB55ADB1C8F88F20050DDA9 /* Resources */, ); buildRules = ( ); dependencies = ( BBB55AE01C8F88F20050DDA9 /* PBXTargetDependency */, ); name = "macOS Tests"; productName = GlueKitTests; productReference = BBB55ADD1C8F88F20050DDA9 /* GlueKit-Test.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; BBB55AF01C8F8BE00050DDA9 /* watchOS */ = { isa = PBXNativeTarget; buildConfigurationList = BBB55AF61C8F8BE00050DDA9 /* Build configuration list for PBXNativeTarget "watchOS" */; buildPhases = ( BBB55AEC1C8F8BE00050DDA9 /* Sources */, BBB55AED1C8F8BE00050DDA9 /* Frameworks */, BBB55AEE1C8F8BE00050DDA9 /* Headers */, BBB55AEF1C8F8BE00050DDA9 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = watchOS; productName = GlueKit; productReference = BBB55AF11C8F8BE00050DDA9 /* GlueKit.framework */; productType = "com.apple.product-type.framework"; }; BBB55AFD1C8F8CBB0050DDA9 /* tvOS */ = { isa = PBXNativeTarget; buildConfigurationList = BBB55B0F1C8F8CBC0050DDA9 /* Build configuration list for PBXNativeTarget "tvOS" */; buildPhases = ( BBB55AF91C8F8CBB0050DDA9 /* Sources */, BBB55AFA1C8F8CBB0050DDA9 /* Frameworks */, BBB55AFB1C8F8CBB0050DDA9 /* Headers */, BBB55AFC1C8F8CBB0050DDA9 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = tvOS; productName = GlueKit; productReference = BBB55AFE1C8F8CBB0050DDA9 /* GlueKit.framework */; productType = "com.apple.product-type.framework"; }; BBB55B061C8F8CBB0050DDA9 /* tvOS Tests */ = { isa = PBXNativeTarget; buildConfigurationList = BBB55B121C8F8CBC0050DDA9 /* Build configuration list for PBXNativeTarget "tvOS Tests" */; buildPhases = ( BBB55B031C8F8CBB0050DDA9 /* Sources */, BBB55B041C8F8CBB0050DDA9 /* Frameworks */, BBB55B051C8F8CBB0050DDA9 /* Resources */, ); buildRules = ( ); dependencies = ( BBB55B0A1C8F8CBC0050DDA9 /* PBXTargetDependency */, ); name = "tvOS Tests"; productName = GlueKitTests; productReference = BBB55B071C8F8CBB0050DDA9 /* GlueKit-Test.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ BBB55AA91C8F80020050DDA9 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0800; LastUpgradeCheck = 0900; ORGANIZATIONNAME = "Károly Lőrentey"; TargetAttributes = { BB351AFB1DB81E67005F083F = { CreatedOnToolsVersion = 8.0; ProvisioningStyle = Automatic; }; BBB55AB11C8F80020050DDA9 = { CreatedOnToolsVersion = 7.3; LastSwiftMigration = 0800; }; BBB55ABB1C8F80020050DDA9 = { CreatedOnToolsVersion = 7.3; LastSwiftMigration = 0800; ProvisioningStyle = Automatic; }; BBB55AD31C8F88F20050DDA9 = { CreatedOnToolsVersion = 7.3; LastSwiftMigration = 0800; ProvisioningStyle = Manual; }; BBB55ADC1C8F88F20050DDA9 = { CreatedOnToolsVersion = 7.3; LastSwiftMigration = 0800; ProvisioningStyle = Automatic; }; BBB55AF01C8F8BE00050DDA9 = { CreatedOnToolsVersion = 7.3; LastSwiftMigration = 0800; }; BBB55AFD1C8F8CBB0050DDA9 = { CreatedOnToolsVersion = 7.3; LastSwiftMigration = 0800; }; BBB55B061C8F8CBB0050DDA9 = { CreatedOnToolsVersion = 7.3; LastSwiftMigration = 0800; ProvisioningStyle = Automatic; }; }; }; buildConfigurationList = BBB55AAC1C8F80020050DDA9 /* Build configuration list for PBXProject "GlueKit" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = BBB55AA81C8F80020050DDA9; productRefGroup = BBB55AB31C8F80020050DDA9 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( BBB55AB11C8F80020050DDA9 /* iOS */, BBB55AD31C8F88F20050DDA9 /* macOS */, BBB55AF01C8F8BE00050DDA9 /* watchOS */, BBB55AFD1C8F8CBB0050DDA9 /* tvOS */, BBB55ABB1C8F80020050DDA9 /* iOS Tests */, BBB55ADC1C8F88F20050DDA9 /* macOS Tests */, BBB55B061C8F8CBB0050DDA9 /* tvOS Tests */, BB351AFB1DB81E67005F083F /* PerformanceTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ BB351AFA1DB81E67005F083F /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; BBB55AB01C8F80020050DDA9 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; BBB55ABA1C8F80020050DDA9 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; BBB55AD21C8F88F20050DDA9 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; BBB55ADB1C8F88F20050DDA9 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; BBB55AEF1C8F8BE00050DDA9 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; BBB55AFC1C8F8CBB0050DDA9 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; BBB55B051C8F8CBB0050DDA9 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ BB351AF81DB81E67005F083F /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( BB351B071DB81EE1005F083F /* GlueKitPerformanceTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; BBB55AAD1C8F80020050DDA9 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( BB9D742F1DBE7B3B00E20D67 /* SetFilteringOnPredicate.swift in Sources */, BBB55B5E1C8FCC820050DDA9 /* ObservableValue.swift in Sources */, BB7E04E61DA725A500BE3051 /* ArrayFilteringOnObservableBool.swift in Sources */, BB948BBC1DD5EFC900B0734C /* ComputedUpdatable.swift in Sources */, BB9D74451DBEB5F300E20D67 /* DistinctUnion.swift in Sources */, BB9D744A1DBEC82C00E20D67 /* ValueMappingForSetField.swift in Sources */, BBB55B821C8FCC820050DDA9 /* TimerSource.swift in Sources */, BB1657E71D71D65900094BAC /* ArrayChangeSeparation.swift in Sources */, BBBBECA61DCA3FF0000B646D /* BufferedSet.swift in Sources */, BB69A83B1D6B90F5001D2821 /* ArrayConcatenation.swift in Sources */, BB6CE3B41DC4C21C00295C55 /* TransformedSink.swift in Sources */, BB82E3E11D98F34E0035DDAD /* ArrayFilteringOnPredicate.swift in Sources */, BBF7B9601EAEA60900073DF8 /* ArrayBasedTableViewDataSource.swift in Sources */, BB9D742E1DBE7B3B00E20D67 /* SetMappingForArrayField.swift in Sources */, BB9D74301DBE7B3B00E20D67 /* SetFilteringOnObservableBool.swift in Sources */, BBB55B761C8FCC820050DDA9 /* SimpleSources.swift in Sources */, BBC2A8291CBC14C600394D24 /* NSObject Glue.swift in Sources */, BB5B02E01F5E3FDE0084D86B /* SetGatheringSource.swift in Sources */, BB69A7FB1D6B1D94001D2821 /* ObservableArray.swift in Sources */, BBF3EB0E1D99ACDE006AC7CD /* RefList.swift in Sources */, BBB55B7E1C8FCC820050DDA9 /* TransformedSource.swift in Sources */, BB9D73E61DBB727300E20D67 /* ChangesSource.swift in Sources */, BBE4DF451DA3A908005EC162 /* ValueChange.swift in Sources */, BBB55B661C8FCC820050DDA9 /* Reference.swift in Sources */, BBCD9E281DAAD2B900E85FB9 /* DistinctValue.swift in Sources */, BBB55B721C8FCC820050DDA9 /* Signal.swift in Sources */, BB9D74331DBE7B3B00E20D67 /* SetSortingByMappingToObservableComparable.swift in Sources */, BB69A8081D6B442B001D2821 /* ArrayVariable.swift in Sources */, BB9D74311DBE7B3B00E20D67 /* SetSortingByComparator.swift in Sources */, BBB55B5A1C8FCC820050DDA9 /* BufferedValue.swift in Sources */, BBCD9E231DAAD26200E85FB9 /* CompositeObservable.swift in Sources */, BB9D74271DBE7B3B00E20D67 /* SetVariable.swift in Sources */, BB9D742D1DBE7B3B00E20D67 /* SetMappingForSetField.swift in Sources */, BBB55B521C8FCC820050DDA9 /* MergedSource.swift in Sources */, BB69A7F31D6B1D7B001D2821 /* Change.swift in Sources */, BB69A7F71D6B1D82001D2821 /* ArrayChange.swift in Sources */, BB7E04E11DA7255D00BE3051 /* ArrayFilteringIndexmap.swift in Sources */, BB9D73E01DBB61C100E20D67 /* TransactionalThing.swift in Sources */, BB9D73CF1DBB38F900E20D67 /* Sink.swift in Sources */, BB69A81D1D6B71F9001D2821 /* SetChange.swift in Sources */, BB69A8101D6B5397001D2821 /* ValueMappingForValueField.swift in Sources */, BBB55B4E1C8FCC820050DDA9 /* Locks.swift in Sources */, BBB55B8E1C8FCC820050DDA9 /* Variable.swift in Sources */, BBF7B9591EACFC3B00073DF8 /* DependentValue.swift in Sources */, BB9D74261DBE7B3B00E20D67 /* UpdatableSet.swift in Sources */, BB9D73C91DBB2EB400E20D67 /* Connect.swift in Sources */, BBBBEC8D1DCA1AEB000B646D /* ValueReference.swift in Sources */, BB318F6B1CB992300086EE83 /* UIBarButtonItem Extensions.swift in Sources */, BB9D742B1DBE7B3B00E20D67 /* SetMappingForSequence.swift in Sources */, BB9D74291DBE7B3B00E20D67 /* SetMappingBase.swift in Sources */, BBBFD14B1EBE673200EC9814 /* SetSortingByComparableField.swift in Sources */, BBB55B561C8FCC820050DDA9 /* NSNotificationCenter Support.swift in Sources */, BBAA827D1C91D43700586903 /* Type Helpers.swift in Sources */, BB5B02DB1F5E3FC50084D86B /* ArrayGatheringSource.swift in Sources */, BB9D742A1DBE7B3B00E20D67 /* SetMappingForValue.swift in Sources */, BBCD9E141DAAC4DC00E85FB9 /* ValueMappingForArrayField.swift in Sources */, BBEA2A3A1DBFE37A00CCCB08 /* BracketingSource.swift in Sources */, BBB55B421C8FCC820050DDA9 /* Connector.swift in Sources */, BB9D73EF1DBDFEE400E20D67 /* TwoWayBinding.swift in Sources */, BBEA2A3F1DBFE38F00CCCB08 /* BufferedSource.swift in Sources */, BB948BC11DD720FE00B0734C /* UISwitch Glue.swift in Sources */, BB9D742C1DBE7B3B00E20D67 /* SetMappingForValueField.swift in Sources */, BBB55B861C8FCC820050DDA9 /* UpdatableValue.swift in Sources */, BB69A8001D6B2377001D2821 /* BufferedArray.swift in Sources */, BBB55B7A1C8FCC820050DDA9 /* Source.swift in Sources */, BB9D74351DBE7B3B00E20D67 /* SetReference.swift in Sources */, BBC3C84E1C93164200E10D59 /* UIControl Glue.swift in Sources */, BB69A8401D6B9A7E001D2821 /* ArrayMappingForValue.swift in Sources */, BBCD9E0F1DAAC33700E85FB9 /* ValueMappingForValue.swift in Sources */, BB69A8191D6B6A99001D2821 /* ArrayReference.swift in Sources */, BB7E04C81DA7233B00BE3051 /* ArrayMappingForArrayField.swift in Sources */, BBCD9E2D1DAAD4BC00E85FB9 /* CompositeUpdatable.swift in Sources */, BBCD9DF91DAA885E00E85FB9 /* ArrayFolding.swift in Sources */, BB9D74341DBE7B3B00E20D67 /* SetFolding.swift in Sources */, BBB4F26F1C99E6CC002532CC /* UIGestureRecognizer Glue.swift in Sources */, BBE9AACA1DC0F0A5000B3DD0 /* Update.swift in Sources */, BBF7B9541EACEB0100073DF8 /* AccumulatedSource.swift in Sources */, BB9D74281DBE7B3B00E20D67 /* ObservableContains.swift in Sources */, BBE4DF401DA3A88C005EC162 /* ObservableType.swift in Sources */, BB9D74241DBE7B3700E20D67 /* ObservableSet.swift in Sources */, BB9D73F41DBE03EB00E20D67 /* DispatchSource.swift in Sources */, BB69A7EF1D6AFF1A001D2821 /* Abstract.swift in Sources */, BB69A80C1D6B472D001D2821 /* ArrayMappingForValueField.swift in Sources */, BB67E5521C9B6A63002B0AB5 /* CADisplayLink Extensions.swift in Sources */, BB69A8041D6B344C001D2821 /* UpdatableArray.swift in Sources */, BBB4F2521C9594A9002532CC /* UIDevice Glue.swift in Sources */, BBF7B9521EACE69B00073DF8 /* UILabel Glue.swift in Sources */, BB3982ED1EC109C4000CDCB5 /* UISearchBar Glue.swift in Sources */, BB9D73FE1DBE061D00E20D67 /* OwnedSink.swift in Sources */, BBCD9E191DAAC50900E85FB9 /* ValueMappingForSourceField.swift in Sources */, BB9D74321DBE7B3B00E20D67 /* SetSortingByMappingToComparable.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; BBB55AB81C8F80020050DDA9 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( BBEBFF791DB77D77008AC632 /* ArrayFilteringTests.swift in Sources */, BBB55BCC1C8FCE2C0050DDA9 /* TestUtilities.swift in Sources */, BBB55BB41C8FCE2C0050DDA9 /* NotificationCenterSupportTests.swift in Sources */, BBE9AAD41DC0F474000B3DD0 /* BufferedSourceTests.swift in Sources */, BBE9AACF1DC0F1F6000B3DD0 /* ChangeTests.swift in Sources */, BBB55BBA1C8FCE2C0050DDA9 /* DistinctTests.swift in Sources */, BBB55BAE1C8FCE2C0050DDA9 /* KVOSupportTests.swift in Sources */, BB88C4131DC3B65F00C7EC3C /* ValueBufferingTests.swift in Sources */, BBCD9E3A1DABC45000E85FB9 /* TypeHelperTests.swift in Sources */, BBB55BC31C8FCE2C0050DDA9 /* SignalTests.swift in Sources */, BBCD9E0B1DAAB6E700E85FB9 /* MockUpdateSink.swift in Sources */, BBE4DF661DA5656B005EC162 /* SetMappingTests.swift in Sources */, BBBBECA21DCA3AA8000B646D /* DispatchSourceTests.swift in Sources */, BBEBFF771DB77D77008AC632 /* ArrayVariableTests.swift in Sources */, BBCD9E361DAAD74600E85FB9 /* CombinedUpdatableTests.swift in Sources */, BBEBFF741DB77D77008AC632 /* ArrayChangeTests.swift in Sources */, BB170F241DC1066D0000443E /* UpdateTests.swift in Sources */, BB471D9A1DA57FF8002550B0 /* SetSortingTests.swift in Sources */, BBEBFF781DB77D77008AC632 /* ArrayMappingTests.swift in Sources */, BBCD9E4E1DABFE8800E85FB9 /* SetVariableTests.swift in Sources */, BB88C40E1DC3A08B00C7EC3C /* ObservableTypeTests.swift in Sources */, BBEBFF7A1DB77D77008AC632 /* ArrayFoldingTests.swift in Sources */, BBCD9DF51DA9104A00E85FB9 /* RefListTests.swift in Sources */, BB170F341DC110BD0000443E /* TestUpdatable.swift in Sources */, BB170F2C1DC10D350000443E /* TestObservable.swift in Sources */, BBCD9E071DAA918400E85FB9 /* SetFoldingTests.swift in Sources */, BBEBFF761DB77D77008AC632 /* ObservableArrayTests.swift in Sources */, BBBBEC9A1DCA223A000B646D /* SetReferenceTests.swift in Sources */, BBEBFF721DB77D77008AC632 /* MockArrayObserver.swift in Sources */, BBBBEC921DCA1D43000B646D /* ValueReferenceTests.swift in Sources */, BB170F281DC106DE0000443E /* TestChange.swift in Sources */, BBBBECAF1DCA446A000B646D /* SetBufferingTests.swift in Sources */, BB170F301DC10DDB0000443E /* ChangesSourceTests.swift in Sources */, BBBBEC961DCA1FEB000B646D /* ArrayReferenceTests.swift in Sources */, BBCD9E3E1DABC81A00E85FB9 /* MockSink.swift in Sources */, BBB55BD51C8FCE2C0050DDA9 /* VariableTests.swift in Sources */, BBCD9E521DAC1B1300E85FB9 /* ConnectorTests.swift in Sources */, BBCD9E321DAAD66500E85FB9 /* CombinedObservableTests.swift in Sources */, BBB55BC61C8FCE2C0050DDA9 /* SimpleSourcesTests.swift in Sources */, BBBBECAB1DCA4168000B646D /* ArrayBufferingTests.swift in Sources */, BBEA2A341DBFC4C300CCCB08 /* AnySourceTests.swift in Sources */, BBEBFF751DB77D77008AC632 /* ArrayChangeSeparationTests.swift in Sources */, BBB55BD21C8FCE2C0050DDA9 /* TwoWayBindingTests.swift in Sources */, BBCD9E421DABD4BE00E85FB9 /* ObservableSetTests.swift in Sources */, BB568AEF1DA693F700BC4B58 /* SetFilteringTests.swift in Sources */, BBBBEC9E1DCA2FAB000B646D /* NSUserDefaultsSupportTests.swift in Sources */, BB170F381DC1545E0000443E /* TransactionStateTests.swift in Sources */, BBB55BCF1C8FCE2C0050DDA9 /* TimerSourceTests.swift in Sources */, BB170F421DC22A300000443E /* UpdatableValueTests.swift in Sources */, BBB55BC01C8FCE2C0050DDA9 /* ValueMappingTests.swift in Sources */, BBB55BC91C8FCE2C0050DDA9 /* SourceOperatorTests.swift in Sources */, BBEBFF7C1DB77D77008AC632 /* ArrayConcatenationTests.swift in Sources */, BB170F3E1DC221180000443E /* ValueChangeTests.swift in Sources */, BBEBFF7B1DB77D77008AC632 /* DistinctUnionTests.swift in Sources */, BBEA2A2B1DBF9E9C00CCCB08 /* AnySinkTests.swift in Sources */, BBB55BB11C8FCE2C0050DDA9 /* MergedSourceTests.swift in Sources */, BB568AF31DA6942700BC4B58 /* MockSetObserver.swift in Sources */, BBEBFF731DB77D77008AC632 /* ArrayModificationTests.swift in Sources */, BB8D366E1DA116CB000D44C5 /* Bookshelf.swift in Sources */, BBB55BB71C8FCE2C0050DDA9 /* ObservableValueTests.swift in Sources */, BBE9AAD81DC0FC17000B3DD0 /* BracketingSourceTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; BBB55ACF1C8F88F20050DDA9 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( BB6CE3C01DC4CD7200295C55 /* TwoWayBinding.swift in Sources */, BB8195B31DD256B700644668 /* UIBarButtonItem Extensions.swift in Sources */, BBF3EB0F1D99ACDE006AC7CD /* RefList.swift in Sources */, BB948BBD1DD5EFC900B0734C /* ComputedUpdatable.swift in Sources */, BB015F421DC526BF00C8C05A /* ArrayFilteringOnObservableBool.swift in Sources */, BB015F491DC52D3500C8C05A /* DistinctUnion.swift in Sources */, BB015F2F1DC4F5C200C8C05A /* UpdatableArray.swift in Sources */, BBBBECA71DCA3FF0000B646D /* BufferedSet.swift in Sources */, BB015F401DC5261900C8C05A /* ArrayFilteringIndexmap.swift in Sources */, BB9D73D01DBB38F900E20D67 /* Sink.swift in Sources */, BB6CE3DA1DC4F2FB00295C55 /* SetChange.swift in Sources */, BB6CE3DB1DC4F2FB00295C55 /* ObservableSet.swift in Sources */, BBF7B9611EAEA60900073DF8 /* ArrayBasedTableViewDataSource.swift in Sources */, BB015F391DC509D000C8C05A /* SetMappingForValue.swift in Sources */, BB015F361DC5004F00C8C05A /* ValueMappingForSetField.swift in Sources */, BB6CE3C11DC4CD7600295C55 /* Variable.swift in Sources */, BB9D741E1DBE644200E20D67 /* NSNotificationCenter Support.swift in Sources */, BB5B02E11F5E3FDE0084D86B /* SetGatheringSource.swift in Sources */, BB6CE3C61DC4CD8D00295C55 /* ValueMappingForValue.swift in Sources */, BB015F301DC4F64200C8C05A /* UpdatableSet.swift in Sources */, BB9FCDCC1F5E54BF001B8781 /* NSButton Glue.swift in Sources */, BB9FCDD01F5ECFF3001B8781 /* NSPopUpButton Glue.swift in Sources */, BB6CE3D91DC4F19D00295C55 /* ObservableArray.swift in Sources */, BB6CE3BC1DC4CD6500295C55 /* TransactionalThing.swift in Sources */, BBEA2A401DBFE38F00CCCB08 /* BufferedSource.swift in Sources */, BB69A7F01D6AFF1A001D2821 /* Abstract.swift in Sources */, BB015F471DC52B6200C8C05A /* ArrayConcatenation.swift in Sources */, BB6CE3D51DC4F17A00295C55 /* ArrayChangeSeparation.swift in Sources */, BB6CE3D41DC4F17700295C55 /* ArrayChange.swift in Sources */, BB015F571DC67DA900C8C05A /* SetFilteringOnObservableBool.swift in Sources */, BB6CE3C51DC4CD8700295C55 /* CompositeUpdatable.swift in Sources */, BB015F4C1DC6233E00C8C05A /* SetMappingForSequence.swift in Sources */, BB6CE3BA1DC4CD3F00295C55 /* ObservableType.swift in Sources */, BB015F551DC67C1000C8C05A /* SetFilteringOnPredicate.swift in Sources */, BB015F501DC678C200C8C05A /* SetMappingForSetField.swift in Sources */, BB6CE3BF1DC4CD6F00295C55 /* UpdatableValue.swift in Sources */, BB9D73CA1DBB2EB400E20D67 /* Connect.swift in Sources */, BB015F3C1DC5106B00C8C05A /* ArrayMappingForValueField.swift in Sources */, BB015F331DC4FC3700C8C05A /* ArrayVariable.swift in Sources */, BB8195B61DD256B700644668 /* CADisplayLink Extensions.swift in Sources */, BB9D73DC1DBB612600E20D67 /* Connector.swift in Sources */, BBBFD14C1EBE673200EC9814 /* SetSortingByComparableField.swift in Sources */, BB6CE3B91DC4CD3B00295C55 /* Update.swift in Sources */, BB6CE3C21DC4CD7A00295C55 /* BufferedValue.swift in Sources */, BB015F511DC679F000C8C05A /* SetMappingForArrayField.swift in Sources */, BB6CE3CE1DC4E4B100295C55 /* Type Helpers.swift in Sources */, BB015F3D1DC512D600C8C05A /* ArrayMappingForArrayField.swift in Sources */, BBF7B95A1EACFC3B00073DF8 /* DependentValue.swift in Sources */, BB015F561DC67C3F00C8C05A /* SetReference.swift in Sources */, BB6CE3BD1DC4CD6900295C55 /* ValueChange.swift in Sources */, BB9D73D51DBB4BB300E20D67 /* MergedSource.swift in Sources */, BBB55B671C8FCC820050DDA9 /* Reference.swift in Sources */, BB6CE3B51DC4C21C00295C55 /* TransformedSink.swift in Sources */, BB9FCDCE1F5EB646001B8781 /* NSControl Glue.swift in Sources */, BB015F371DC506B400C8C05A /* ValueMappingForArrayField.swift in Sources */, BB015F5F1DC687EE00C8C05A /* SetSortingByComparator.swift in Sources */, BB5B02DC1F5E3FC50084D86B /* ArrayGatheringSource.swift in Sources */, BB015F3A1DC50B1200C8C05A /* ArrayMappingForValue.swift in Sources */, BB015F411DC5262400C8C05A /* ArrayFilteringOnPredicate.swift in Sources */, BB015F5D1DC686CD00C8C05A /* SetSortingByMappingToObservableComparable.swift in Sources */, BB8195B21DD256B700644668 /* UIControl Glue.swift in Sources */, BB948BC21DD7213200B0734C /* UISwitch Glue.swift in Sources */, BB015F321DC4FC3100C8C05A /* SetVariable.swift in Sources */, BB6CE3BB1DC4CD4800295C55 /* ChangesSource.swift in Sources */, BB6CE3C41DC4CD8300295C55 /* CompositeObservable.swift in Sources */, BB6CE3C81DC4D2D400295C55 /* ValueMappingForValueField.swift in Sources */, BB6139831F5E1B3F005455D5 /* NSTextField Glue.swift in Sources */, BB9D73F51DBE03EB00E20D67 /* DispatchSource.swift in Sources */, BB9D73FF1DBE061D00E20D67 /* OwnedSink.swift in Sources */, BB015F451DC52ADD00C8C05A /* ArrayFolding.swift in Sources */, BB9D73F81DBE041A00E20D67 /* TimerSource.swift in Sources */, BB9D73D61DBB519500E20D67 /* TransformedSource.swift in Sources */, BB6CE3C31DC4CD7E00295C55 /* DistinctValue.swift in Sources */, BB9D74201DBE654600E20D67 /* NSObject Glue.swift in Sources */, BB9D73D31DBB3DEA00E20D67 /* Signal.swift in Sources */, BBBBEC8E1DCA1AEB000B646D /* ValueReference.swift in Sources */, BBF7B9551EACEB0100073DF8 /* AccumulatedSource.swift in Sources */, BB3982EE1EC109C7000CDCB5 /* UISearchBar Glue.swift in Sources */, BB015F4F1DC677A900C8C05A /* SetMappingForValueField.swift in Sources */, BB8195B41DD256B700644668 /* UIDevice Glue.swift in Sources */, BB6CE3BE1DC4CD6B00295C55 /* ObservableValue.swift in Sources */, BB015F5C1DC6868F00C8C05A /* SetSortingByMappingToComparable.swift in Sources */, BB015F341DC4FDCE00C8C05A /* SetMappingBase.swift in Sources */, BB8195B51DD256B700644668 /* UIGestureRecognizer Glue.swift in Sources */, BB015F5A1DC684E200C8C05A /* SetFolding.swift in Sources */, BBEA2A3B1DBFE37B00CCCB08 /* BracketingSource.swift in Sources */, BBB55B4F1C8FCC820050DDA9 /* Locks.swift in Sources */, BBB55B7B1C8FCC820050DDA9 /* Source.swift in Sources */, BB9D73D41DBB4A3700E20D67 /* SimpleSources.swift in Sources */, BB6CE3B81DC4CD3900295C55 /* Change.swift in Sources */, BB6CE3C71DC4D09500295C55 /* ValueMappingForSourceField.swift in Sources */, BB015F381DC508AC00C8C05A /* BufferedArray.swift in Sources */, BB015F431DC5281F00C8C05A /* ArrayReference.swift in Sources */, BB015F351DC4FDD300C8C05A /* ObservableContains.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; BBB55AD91C8F88F20050DDA9 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( BBBBEC931DCA1D43000B646D /* ValueReferenceTests.swift in Sources */, BB170F3C1DC220B20000443E /* ObservableValueTests.swift in Sources */, BB170F3B1DC220A80000443E /* MockUpdateSink.swift in Sources */, BB6CE3D21DC4E73600295C55 /* NotificationCenterSupportTests.swift in Sources */, BBBBEC971DCA1FEB000B646D /* ArrayReferenceTests.swift in Sources */, BBEA2A291DBF9ADA00CCCB08 /* MockSink.swift in Sources */, BB6CE3D31DC4E79400295C55 /* ValueMappingTests.swift in Sources */, BB170F431DC22A300000443E /* UpdatableValueTests.swift in Sources */, BB170F461DC255200000443E /* TwoWayBindingTests.swift in Sources */, BB170F251DC1066D0000443E /* UpdateTests.swift in Sources */, BB015F541DC67ACA00C8C05A /* SetMappingTests.swift in Sources */, BBB55BCD1C8FCE2C0050DDA9 /* TestUtilities.swift in Sources */, BB6CE3CA1DC4DF4400295C55 /* CombinedObservableTests.swift in Sources */, BB170F2D1DC10D350000443E /* TestObservable.swift in Sources */, BBE9AAD51DC0F474000B3DD0 /* BufferedSourceTests.swift in Sources */, BBE9AAD01DC0F1F6000B3DD0 /* ChangeTests.swift in Sources */, BBCD9DF61DA9104A00E85FB9 /* RefListTests.swift in Sources */, BBBBEC9B1DCA223A000B646D /* SetReferenceTests.swift in Sources */, BB8195C01DD257A300644668 /* ConnectorTests.swift in Sources */, BB015F531DC67A6400C8C05A /* SetVariableTests.swift in Sources */, BB015F3B1DC50C6700C8C05A /* MockArrayObserver.swift in Sources */, BB170F391DC1545E0000443E /* TransactionStateTests.swift in Sources */, BB170F311DC10DDB0000443E /* ChangesSourceTests.swift in Sources */, BB015F5E1DC687B100C8C05A /* SetSortingTests.swift in Sources */, BB88C40F1DC3A08B00C7EC3C /* ObservableTypeTests.swift in Sources */, BBEA2A371DBFC71000CCCB08 /* SimpleSourcesTests.swift in Sources */, BBEA2A351DBFC4C300CCCB08 /* AnySourceTests.swift in Sources */, BBEA2A381DBFC7B800CCCB08 /* MergedSourceTests.swift in Sources */, BB170F291DC106DE0000443E /* TestChange.swift in Sources */, BB6CE3D81DC4F18B00295C55 /* ArrayChangeSeparationTests.swift in Sources */, BB015F591DC684DC00C8C05A /* SetFoldingTests.swift in Sources */, BB015F3F1DC51D8300C8C05A /* ArrayVariableTests.swift in Sources */, BBE9AAD91DC0FC17000B3DD0 /* BracketingSourceTests.swift in Sources */, BB170F351DC110BD0000443E /* TestUpdatable.swift in Sources */, BB015F4A1DC52DC500C8C05A /* DistinctUnionTests.swift in Sources */, BB015F4B1DC52FA000C8C05A /* ObservableArrayTests.swift in Sources */, BB015F601DC689F600C8C05A /* Bookshelf.swift in Sources */, BB6CE3D01DC4E67700295C55 /* TimerSourceTests.swift in Sources */, BB015F461DC52B4500C8C05A /* ArrayFoldingTests.swift in Sources */, BB015F311DC4F90200C8C05A /* MockSetObserver.swift in Sources */, BB170F451DC239BC0000443E /* DistinctTests.swift in Sources */, BB6CE3D11DC4E72100295C55 /* KVOSupportTests.swift in Sources */, BBBBECA31DCA3AA8000B646D /* DispatchSourceTests.swift in Sources */, BB015F481DC52CE200C8C05A /* ArrayConcatenationTests.swift in Sources */, BB015F581DC67F0500C8C05A /* SetFilteringTests.swift in Sources */, BB015F3E1DC5147700C8C05A /* ArrayMappingTests.swift in Sources */, BB88C4111DC3A84C00C7EC3C /* VariableTests.swift in Sources */, BBEA2A2C1DBF9E9C00CCCB08 /* AnySinkTests.swift in Sources */, BBBBECB01DCA446A000B646D /* SetBufferingTests.swift in Sources */, BB015F441DC529CF00C8C05A /* ArrayFilteringTests.swift in Sources */, BBBBEC9F1DCA2FAB000B646D /* NSUserDefaultsSupportTests.swift in Sources */, BB015F2E1DC4F5BA00C8C05A /* ObservableSetTests.swift in Sources */, BBEA2A4D1DC0BB4D00CCCB08 /* SourceOperatorTests.swift in Sources */, BBEA2A321DBFC3BB00CCCB08 /* SignalTests.swift in Sources */, BB6CE3D71DC4F18B00295C55 /* ArrayChangeTests.swift in Sources */, BBBBECAC1DCA4168000B646D /* ArrayBufferingTests.swift in Sources */, BB6CE3CC1DC4E48200295C55 /* TypeHelperTests.swift in Sources */, BB88C4141DC3B65F00C7EC3C /* ValueBufferingTests.swift in Sources */, BB170F3F1DC221180000443E /* ValueChangeTests.swift in Sources */, BB6CE3CB1DC4DF4800295C55 /* CombinedUpdatableTests.swift in Sources */, BB6CE3D61DC4F18B00295C55 /* ArrayModificationTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; BBB55AEC1C8F8BE00050DDA9 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( BBCD9E251DAAD26200E85FB9 /* CompositeObservable.swift in Sources */, BB8195B81DD256B800644668 /* UIBarButtonItem Extensions.swift in Sources */, BB69A81B1D6B6A9A001D2821 /* ArrayReference.swift in Sources */, BB948BBE1DD5EFC900B0734C /* ComputedUpdatable.swift in Sources */, BBB55B601C8FCC820050DDA9 /* ObservableValue.swift in Sources */, BB69A7F91D6B1D83001D2821 /* ArrayChange.swift in Sources */, BBB55B841C8FCC820050DDA9 /* TimerSource.swift in Sources */, BB7E04D41DA7240C00BE3051 /* SetMappingForSequence.swift in Sources */, BBBBECA81DCA3FF0000B646D /* BufferedSet.swift in Sources */, BB69A81F1D6B71FA001D2821 /* SetChange.swift in Sources */, BB9D74001DBE061D00E20D67 /* OwnedSink.swift in Sources */, BB69A8061D6B344D001D2821 /* UpdatableArray.swift in Sources */, BBF7B9621EAEA60900073DF8 /* ArrayBasedTableViewDataSource.swift in Sources */, BB9D73F61DBE03EB00E20D67 /* DispatchSource.swift in Sources */, BB9D73E81DBB727300E20D67 /* ChangesSource.swift in Sources */, BB5B02E21F5E3FDE0084D86B /* SetGatheringSource.swift in Sources */, BBEA2A411DBFE38F00CCCB08 /* BufferedSource.swift in Sources */, BBE4DF421DA3A88C005EC162 /* ObservableType.swift in Sources */, BBB55B781C8FCC820050DDA9 /* SimpleSources.swift in Sources */, BBE4DF471DA3A908005EC162 /* ValueChange.swift in Sources */, BBB55B801C8FCC820050DDA9 /* TransformedSource.swift in Sources */, BBF400661DA108E900DA0B2C /* ObservableContains.swift in Sources */, BB6CE3B61DC4C21C00295C55 /* TransformedSink.swift in Sources */, BB5B02DD1F5E3FC50084D86B /* ArrayGatheringSource.swift in Sources */, BB69A80E1D6B472F001D2821 /* ArrayMappingForValueField.swift in Sources */, BBF3EB101D99ACDE006AC7CD /* RefList.swift in Sources */, BB69A7F11D6AFF1A001D2821 /* Abstract.swift in Sources */, BBA033631DA7278C00E83AC1 /* SetSortingByMappingToObservableComparable.swift in Sources */, BB7E04CA1DA7233B00BE3051 /* ArrayMappingForArrayField.swift in Sources */, BBE9AACC1DC0F0A5000B3DD0 /* Update.swift in Sources */, BB69A80A1D6B442C001D2821 /* ArrayVariable.swift in Sources */, BBCD9DFB1DAA885E00E85FB9 /* ArrayFolding.swift in Sources */, BBCD9E201DAAC7E600E85FB9 /* ValueMappingForSetField.swift in Sources */, BBE4DF4C1DA409EE005EC162 /* DistinctUnion.swift in Sources */, BB9D73D11DBB38F900E20D67 /* Sink.swift in Sources */, BB69A83D1D6B90F5001D2821 /* ArrayConcatenation.swift in Sources */, BB69A8281D6B779A001D2821 /* UpdatableSet.swift in Sources */, BB69A8341D6B7F12001D2821 /* SetReference.swift in Sources */, BB7E04CF1DA723F300BE3051 /* SetMappingForValueField.swift in Sources */, BBB55B681C8FCC820050DDA9 /* Reference.swift in Sources */, BBBFD14D1EBE673200EC9814 /* SetSortingByComparableField.swift in Sources */, BB8195BB1DD256B800644668 /* CADisplayLink Extensions.swift in Sources */, BB69A7FD1D6B1D95001D2821 /* ObservableArray.swift in Sources */, BBA0335E1DA7276200E83AC1 /* SetSortingByComparator.swift in Sources */, BB7E04E81DA725A500BE3051 /* ArrayFilteringOnObservableBool.swift in Sources */, BB9D73E21DBB61C100E20D67 /* TransactionalThing.swift in Sources */, BBF7B95B1EACFC3B00073DF8 /* DependentValue.swift in Sources */, BBB55B741C8FCC820050DDA9 /* Signal.swift in Sources */, BBB55B5C1C8FCC820050DDA9 /* BufferedValue.swift in Sources */, BB69A82C1D6B7CAE001D2821 /* SetVariable.swift in Sources */, BBCD9E111DAAC33700E85FB9 /* ValueMappingForValue.swift in Sources */, BBB55B541C8FCC820050DDA9 /* MergedSource.swift in Sources */, BB69A8301D6B7E36001D2821 /* SetSortingByMappingToComparable.swift in Sources */, BB69A8231D6B7203001D2821 /* ObservableSet.swift in Sources */, BBB55B501C8FCC820050DDA9 /* Locks.swift in Sources */, BBB55B901C8FCC820050DDA9 /* Variable.swift in Sources */, BB7E04D91DA7242F00BE3051 /* SetMappingForSetField.swift in Sources */, BBCD9E2F1DAAD4BC00E85FB9 /* CompositeUpdatable.swift in Sources */, BB8195B71DD256B800644668 /* UIControl Glue.swift in Sources */, BB948BC31DD7213200B0734C /* UISwitch Glue.swift in Sources */, BBB55B581C8FCC820050DDA9 /* NSNotificationCenter Support.swift in Sources */, BB1657E91D71D65900094BAC /* ArrayChangeSeparation.swift in Sources */, BB69A8381D6B7F9D001D2821 /* SetFilteringOnPredicate.swift in Sources */, BB9D73F11DBDFEE400E20D67 /* TwoWayBinding.swift in Sources */, BBEA2A3C1DBFE37B00CCCB08 /* BracketingSource.swift in Sources */, BB7E04E31DA7255D00BE3051 /* ArrayFilteringIndexmap.swift in Sources */, BBCD9E161DAAC4DC00E85FB9 /* ValueMappingForArrayField.swift in Sources */, BB82E3E31D98F34E0035DDAD /* ArrayFilteringOnPredicate.swift in Sources */, BBA033591DA726AA00E83AC1 /* SetFilteringOnObservableBool.swift in Sources */, BBAA827F1C91D43700586903 /* Type Helpers.swift in Sources */, BB69A8141D6B539A001D2821 /* ValueMappingForValueField.swift in Sources */, BB69A8021D6B2377001D2821 /* BufferedArray.swift in Sources */, BBF7B9561EACEB0100073DF8 /* AccumulatedSource.swift in Sources */, BB3982EF1EC109C8000CDCB5 /* UISearchBar Glue.swift in Sources */, BBBBEC8F1DCA1AEB000B646D /* ValueReference.swift in Sources */, BBE4DF591DA55384005EC162 /* SetMappingForValue.swift in Sources */, BB8195B91DD256B800644668 /* UIDevice Glue.swift in Sources */, BB69A8421D6B9A7E001D2821 /* ArrayMappingForValue.swift in Sources */, BBB55B441C8FCC820050DDA9 /* Connector.swift in Sources */, BB69A7F51D6B1D7C001D2821 /* Change.swift in Sources */, BB8195BA1DD256B800644668 /* UIGestureRecognizer Glue.swift in Sources */, BBE4DF5E1DA55DA3005EC162 /* SetMappingBase.swift in Sources */, BBCD9E041DAA910500E85FB9 /* SetFolding.swift in Sources */, BB7E04DE1DA7244A00BE3051 /* SetMappingForArrayField.swift in Sources */, BBB55B881C8FCC820050DDA9 /* UpdatableValue.swift in Sources */, BBCD9E2A1DAAD2B900E85FB9 /* DistinctValue.swift in Sources */, BB9D73CB1DBB2EB400E20D67 /* Connect.swift in Sources */, BBB55B7C1C8FCC820050DDA9 /* Source.swift in Sources */, BBCD9E1B1DAAC50900E85FB9 /* ValueMappingForSourceField.swift in Sources */, BBC2A82B1CBC14C600394D24 /* NSObject Glue.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; BBB55AF91C8F8CBB0050DDA9 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( BB69A7F21D6AFF1A001D2821 /* Abstract.swift in Sources */, BB9D73F71DBE03EB00E20D67 /* DispatchSource.swift in Sources */, BBCD9DFC1DAA885E00E85FB9 /* ArrayFolding.swift in Sources */, BB948BBF1DD5EFC900B0734C /* ComputedUpdatable.swift in Sources */, BBCD9E171DAAC4DC00E85FB9 /* ValueMappingForArrayField.swift in Sources */, BB67E5531C9B6A63002B0AB5 /* CADisplayLink Extensions.swift in Sources */, BBE4DF4D1DA409EE005EC162 /* DistinctUnion.swift in Sources */, BB69A83E1D6B90F5001D2821 /* ArrayConcatenation.swift in Sources */, BB69A7F61D6B1D7C001D2821 /* Change.swift in Sources */, BBCD9E211DAAC7E600E85FB9 /* ValueMappingForSetField.swift in Sources */, BB69A80B1D6B442D001D2821 /* ArrayVariable.swift in Sources */, BB8195BE1DD256B900644668 /* UIDevice Glue.swift in Sources */, BBF7B9631EAEA60900073DF8 /* ArrayBasedTableViewDataSource.swift in Sources */, BB9D74011DBE061D00E20D67 /* OwnedSink.swift in Sources */, BB69A8311D6B7E37001D2821 /* SetSortingByMappingToComparable.swift in Sources */, BB5B02E31F5E3FDE0084D86B /* SetGatheringSource.swift in Sources */, BBEA2A3D1DBFE37B00CCCB08 /* BracketingSource.swift in Sources */, BBB55B611C8FCC820050DDA9 /* ObservableValue.swift in Sources */, BBCD9E1C1DAAC50900E85FB9 /* ValueMappingForSourceField.swift in Sources */, BB9D73E91DBB727300E20D67 /* ChangesSource.swift in Sources */, BBCD9E2B1DAAD2B900E85FB9 /* DistinctValue.swift in Sources */, BBB55B851C8FCC820050DDA9 /* TimerSource.swift in Sources */, BBB55B791C8FCC820050DDA9 /* SimpleSources.swift in Sources */, BB5B02DE1F5E3FC50084D86B /* ArrayGatheringSource.swift in Sources */, BB7E04D51DA7240C00BE3051 /* SetMappingForSequence.swift in Sources */, BBB55B811C8FCC820050DDA9 /* TransformedSource.swift in Sources */, BB69A8031D6B2377001D2821 /* BufferedArray.swift in Sources */, BB69A82D1D6B7CAF001D2821 /* SetVariable.swift in Sources */, BBCD9E301DAAD4BC00E85FB9 /* CompositeUpdatable.swift in Sources */, BBE4DF431DA3A88C005EC162 /* ObservableType.swift in Sources */, BBB55B691C8FCC820050DDA9 /* Reference.swift in Sources */, BB9D73CC1DBB2EB400E20D67 /* Connect.swift in Sources */, BB7E04E41DA7255D00BE3051 /* ArrayFilteringIndexmap.swift in Sources */, BB69A7FA1D6B1D83001D2821 /* ArrayChange.swift in Sources */, BBEA2A421DBFE38F00CCCB08 /* BufferedSource.swift in Sources */, BB8195BC1DD256B900644668 /* UIControl Glue.swift in Sources */, BBB55B751C8FCC820050DDA9 /* Signal.swift in Sources */, BBCD9E261DAAD26200E85FB9 /* CompositeObservable.swift in Sources */, BBCD9E121DAAC33700E85FB9 /* ValueMappingForValue.swift in Sources */, BB69A8431D6B9A7E001D2821 /* ArrayMappingForValue.swift in Sources */, BBBFD14E1EBE673200EC9814 /* SetSortingByComparableField.swift in Sources */, BBB55B5D1C8FCC820050DDA9 /* BufferedValue.swift in Sources */, BB9D73D21DBB38F900E20D67 /* Sink.swift in Sources */, BBCD9E051DAA910500E85FB9 /* SetFolding.swift in Sources */, BB69A7FE1D6B1D95001D2821 /* ObservableArray.swift in Sources */, BBF3EB111D99ACDE006AC7CD /* RefList.swift in Sources */, BBF7B95C1EACFC3B00073DF8 /* DependentValue.swift in Sources */, BB8195BD1DD256B900644668 /* UIBarButtonItem Extensions.swift in Sources */, BBF400671DA108E900DA0B2C /* ObservableContains.swift in Sources */, BB6CE3B71DC4C21C00295C55 /* TransformedSink.swift in Sources */, BB7E04E91DA725A500BE3051 /* ArrayFilteringOnObservableBool.swift in Sources */, BBB55B551C8FCC820050DDA9 /* MergedSource.swift in Sources */, BBE9AACD1DC0F0A5000B3DD0 /* Update.swift in Sources */, BBE4DF5F1DA55DA3005EC162 /* SetMappingBase.swift in Sources */, BB69A80F1D6B472F001D2821 /* ArrayMappingForValueField.swift in Sources */, BBA033641DA7278C00E83AC1 /* SetSortingByMappingToObservableComparable.swift in Sources */, BB1657EA1D71D65900094BAC /* ArrayChangeSeparation.swift in Sources */, BBA0335F1DA7276200E83AC1 /* SetSortingByComparator.swift in Sources */, BB69A8351D6B7F12001D2821 /* SetReference.swift in Sources */, BB948BC41DD7213300B0734C /* UISwitch Glue.swift in Sources */, BBE4DF481DA3A908005EC162 /* ValueChange.swift in Sources */, BBB55B511C8FCC820050DDA9 /* Locks.swift in Sources */, BB69A8071D6B344E001D2821 /* UpdatableArray.swift in Sources */, BBB55B911C8FCC820050DDA9 /* Variable.swift in Sources */, BBB55B591C8FCC820050DDA9 /* NSNotificationCenter Support.swift in Sources */, BBAA82801C91D43700586903 /* Type Helpers.swift in Sources */, BBB55B451C8FCC820050DDA9 /* Connector.swift in Sources */, BB7E04CB1DA7233B00BE3051 /* ArrayMappingForArrayField.swift in Sources */, BBB55B891C8FCC820050DDA9 /* UpdatableValue.swift in Sources */, BB9D73E31DBB61C100E20D67 /* TransactionalThing.swift in Sources */, BB69A8201D6B71FB001D2821 /* SetChange.swift in Sources */, BBBBEC901DCA1AEB000B646D /* ValueReference.swift in Sources */, BBF7B9571EACEB0100073DF8 /* AccumulatedSource.swift in Sources */, BB3982F01EC109C8000CDCB5 /* UISearchBar Glue.swift in Sources */, BB7E04DF1DA7244A00BE3051 /* SetMappingForArrayField.swift in Sources */, BB69A8131D6B539A001D2821 /* ValueMappingForValueField.swift in Sources */, BBA0335A1DA726AA00E83AC1 /* SetFilteringOnObservableBool.swift in Sources */, BB9D73F21DBDFEE400E20D67 /* TwoWayBinding.swift in Sources */, BB7E04DA1DA7242F00BE3051 /* SetMappingForSetField.swift in Sources */, BBB55B7D1C8FCC820050DDA9 /* Source.swift in Sources */, BBC2A82C1CBC14C600394D24 /* NSObject Glue.swift in Sources */, BBE4DF5A1DA55384005EC162 /* SetMappingForValue.swift in Sources */, BB69A81C1D6B6A9B001D2821 /* ArrayReference.swift in Sources */, BB69A8391D6B7F9E001D2821 /* SetFilteringOnPredicate.swift in Sources */, BBBBECA91DCA3FF0000B646D /* BufferedSet.swift in Sources */, BB7E04D01DA723F300BE3051 /* SetMappingForValueField.swift in Sources */, BB8195BF1DD256B900644668 /* UIGestureRecognizer Glue.swift in Sources */, BB69A8241D6B7203001D2821 /* ObservableSet.swift in Sources */, BB69A8291D6B779B001D2821 /* UpdatableSet.swift in Sources */, BB82E3E41D98F34E0035DDAD /* ArrayFilteringOnPredicate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; BBB55B031C8F8CBB0050DDA9 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( BBB55BCE1C8FCE2C0050DDA9 /* TestUtilities.swift in Sources */, BBB55BB61C8FCE2C0050DDA9 /* NotificationCenterSupportTests.swift in Sources */, BBB55BBC1C8FCE2C0050DDA9 /* DistinctTests.swift in Sources */, BBE9AAD61DC0F474000B3DD0 /* BufferedSourceTests.swift in Sources */, BBE9AAD11DC0F1F6000B3DD0 /* ChangeTests.swift in Sources */, BBB55BB01C8FCE2C0050DDA9 /* KVOSupportTests.swift in Sources */, BBCD9E3C1DABC45000E85FB9 /* TypeHelperTests.swift in Sources */, BB88C4151DC3B65F00C7EC3C /* ValueBufferingTests.swift in Sources */, BBB55BC51C8FCE2C0050DDA9 /* SignalTests.swift in Sources */, BBCD9E0D1DAAB6E700E85FB9 /* MockUpdateSink.swift in Sources */, BBC36AED1DA8453000AB3E9D /* ArrayChangeSeparationTests.swift in Sources */, BBE4DF681DA5656D005EC162 /* SetMappingTests.swift in Sources */, BBBBECA41DCA3AA8000B646D /* DispatchSourceTests.swift in Sources */, BBCD9E381DAAD74600E85FB9 /* CombinedUpdatableTests.swift in Sources */, BBCD9E001DAA8F7800E85FB9 /* ArrayFoldingTests.swift in Sources */, BB471D9C1DA57FF8002550B0 /* SetSortingTests.swift in Sources */, BB170F261DC1066D0000443E /* UpdateTests.swift in Sources */, BBCD9E501DABFE8800E85FB9 /* SetVariableTests.swift in Sources */, BBC4AE501D9A829500FF7DE0 /* ArrayFilteringTests.swift in Sources */, BBCD9DF71DA9104A00E85FB9 /* RefListTests.swift in Sources */, BB88C4101DC3A08B00C7EC3C /* ObservableTypeTests.swift in Sources */, BBB55BBF1C8FCE2C0050DDA9 /* ArrayMappingTests.swift in Sources */, BBCD9E091DAA918400E85FB9 /* SetFoldingTests.swift in Sources */, BB170F361DC110BD0000443E /* TestUpdatable.swift in Sources */, BB170F2E1DC10D350000443E /* TestObservable.swift in Sources */, BBA033681DA7CFFE00E83AC1 /* ArrayModificationTests.swift in Sources */, BBCD9E401DABC81A00E85FB9 /* MockSink.swift in Sources */, BBBBEC9C1DCA223A000B646D /* SetReferenceTests.swift in Sources */, BBB55BAA1C8FCE2C0050DDA9 /* ArrayVariableTests.swift in Sources */, BBBBEC941DCA1D43000B646D /* ValueReferenceTests.swift in Sources */, BB170F2A1DC106DE0000443E /* TestChange.swift in Sources */, BBBBECB11DCA446A000B646D /* SetBufferingTests.swift in Sources */, BB170F321DC10DDB0000443E /* ChangesSourceTests.swift in Sources */, BBBBEC981DCA1FEB000B646D /* ArrayReferenceTests.swift in Sources */, BBB55BD71C8FCE2C0050DDA9 /* VariableTests.swift in Sources */, BBCD9E541DAC1B1300E85FB9 /* ConnectorTests.swift in Sources */, BBCD9E341DAAD66500E85FB9 /* CombinedObservableTests.swift in Sources */, BBB55BC81C8FCE2C0050DDA9 /* SimpleSourcesTests.swift in Sources */, BBB55BD41C8FCE2C0050DDA9 /* TwoWayBindingTests.swift in Sources */, BBBBECAD1DCA4168000B646D /* ArrayBufferingTests.swift in Sources */, BBEA2A361DBFC4C300CCCB08 /* AnySourceTests.swift in Sources */, BBCD9E441DABD4BE00E85FB9 /* ObservableSetTests.swift in Sources */, BB568AF11DA693F700BC4B58 /* SetFilteringTests.swift in Sources */, BBB55BD11C8FCE2C0050DDA9 /* TimerSourceTests.swift in Sources */, BBCD9E4C1DABFA2900E85FB9 /* ArrayConcatenationTests.swift in Sources */, BBBBECA01DCA2FAB000B646D /* NSUserDefaultsSupportTests.swift in Sources */, BB170F3A1DC1545E0000443E /* TransactionStateTests.swift in Sources */, BBB55BC21C8FCE2C0050DDA9 /* ValueMappingTests.swift in Sources */, BB170F441DC22A300000443E /* UpdatableValueTests.swift in Sources */, BBB55BCB1C8FCE2C0050DDA9 /* SourceOperatorTests.swift in Sources */, BBB55BB31C8FCE2C0050DDA9 /* MergedSourceTests.swift in Sources */, BBE4DF551DA41373005EC162 /* DistinctUnionTests.swift in Sources */, BB170F401DC221180000443E /* ValueChangeTests.swift in Sources */, BB568AF91DA6A9EE00BC4B58 /* MockArrayObserver.swift in Sources */, BBEA2A2D1DBF9E9C00CCCB08 /* AnySinkTests.swift in Sources */, BB568AF51DA6942700BC4B58 /* MockSetObserver.swift in Sources */, BB8D36701DA116CB000D44C5 /* Bookshelf.swift in Sources */, BBCD9E481DABEA5500E85FB9 /* ObservableArrayTests.swift in Sources */, BBB55BB91C8FCE2C0050DDA9 /* ObservableValueTests.swift in Sources */, BBA0336C1DA7D03300E83AC1 /* ArrayChangeTests.swift in Sources */, BBE9AADA1DC0FC17000B3DD0 /* BracketingSourceTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ BB351B031DB81E67005F083F /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = BBB55AD31C8F88F20050DDA9 /* macOS */; targetProxy = BB351B021DB81E67005F083F /* PBXContainerItemProxy */; }; BBB55ABF1C8F80020050DDA9 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = BBB55AB11C8F80020050DDA9 /* iOS */; targetProxy = BBB55ABE1C8F80020050DDA9 /* PBXContainerItemProxy */; }; BBB55AE01C8F88F20050DDA9 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = BBB55AD31C8F88F20050DDA9 /* macOS */; targetProxy = BBB55ADF1C8F88F20050DDA9 /* PBXContainerItemProxy */; }; BBB55B0A1C8F8CBC0050DDA9 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = BBB55AFD1C8F8CBB0050DDA9 /* tvOS */; targetProxy = BBB55B091C8F8CBC0050DDA9 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ BB351B051DB81E67005F083F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = NO; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Tests/PerformanceTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.12; PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER_BASE).PerformanceTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; }; name = Debug; }; BB351B061DB81E67005F083F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = NO; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Tests/PerformanceTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.12; PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER_BASE).PerformanceTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; }; name = Release; }; BBB55AC41C8F80020050DDA9 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = BBB55ACC1C8F80660050DDA9 /* version.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = "$(BUILD_NUMBER)"; DEBUG_INFORMATION_FORMAT = dwarf; DYLIB_COMPATIBILITY_VERSION = "$(BUILD_NUMBER)"; DYLIB_CURRENT_VERSION = "$(BUILD_NUMBER)"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 4.0; VERSION_INFO_PREFIX = ""; }; name = Debug; }; BBB55AC51C8F80020050DDA9 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = BBB55ACC1C8F80660050DDA9 /* version.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = "$(BUILD_NUMBER)"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DYLIB_COMPATIBILITY_VERSION = "$(BUILD_NUMBER)"; DYLIB_CURRENT_VERSION = "$(BUILD_NUMBER)"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MTL_ENABLE_DEBUG_INFO = NO; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_VERSION = 4.0; VALIDATE_PRODUCT = YES; VERSION_INFO_PREFIX = ""; }; name = Release; }; BBB55AC71C8F80020050DDA9 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = Sources/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER_BASE).$(PLATFORM_DISPLAY_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(PROJECT_NAME)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; BBB55AC81C8F80020050DDA9 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = Sources/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER_BASE).$(PLATFORM_DISPLAY_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(PROJECT_NAME)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; BBB55ACA1C8F80020050DDA9 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = NO; CLANG_ENABLE_MODULES = YES; INFOPLIST_FILE = Tests/GlueKitTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER_BASE).Tests"; PRODUCT_NAME = "$(PROJECT_NAME)-Test"; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; BBB55ACB1C8F80020050DDA9 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = NO; CLANG_ENABLE_MODULES = YES; INFOPLIST_FILE = Tests/GlueKitTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER_BASE).Tests"; PRODUCT_NAME = "$(PROJECT_NAME)-Test"; SDKROOT = iphoneos; }; name = Release; }; BBB55AE61C8F88F20050DDA9 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; COMBINE_HIDPI_IMAGES = YES; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; FRAMEWORK_VERSION = A; INFOPLIST_FILE = Sources/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER_BASE).$(PLATFORM_DISPLAY_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(PROJECT_NAME)"; SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; BBB55AE71C8F88F20050DDA9 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; COMBINE_HIDPI_IMAGES = YES; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; FRAMEWORK_VERSION = A; INFOPLIST_FILE = Sources/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER_BASE).$(PLATFORM_DISPLAY_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(PROJECT_NAME)"; SDKROOT = macosx; SKIP_INSTALL = YES; }; name = Release; }; BBB55AE91C8F88F20050DDA9 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = NO; CLANG_ENABLE_MODULES = YES; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Tests/GlueKitTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER_BASE).Tests"; PRODUCT_NAME = "$(PROJECT_NAME)-Test"; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; BBB55AEA1C8F88F20050DDA9 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = NO; CLANG_ENABLE_MODULES = YES; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Tests/GlueKitTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER_BASE).Tests"; PRODUCT_NAME = "$(PROJECT_NAME)-Test"; SDKROOT = macosx; }; name = Release; }; BBB55AF71C8F8BE00050DDA9 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = Sources/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER_BASE).$(PLATFORM_DISPLAY_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(PROJECT_NAME)"; SDKROOT = watchos; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = 4; }; name = Debug; }; BBB55AF81C8F8BE00050DDA9 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = Sources/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER_BASE).$(PLATFORM_DISPLAY_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(PROJECT_NAME)"; SDKROOT = watchos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = 4; }; name = Release; }; BBB55B101C8F8CBC0050DDA9 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; DEFINES_MODULE = YES; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = Sources/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER_BASE).$(PLATFORM_DISPLAY_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(PROJECT_NAME)"; SDKROOT = appletvos; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = 3; }; name = Debug; }; BBB55B111C8F8CBC0050DDA9 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; DEFINES_MODULE = YES; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = Sources/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER_BASE).$(PLATFORM_DISPLAY_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(PROJECT_NAME)"; SDKROOT = appletvos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = 3; }; name = Release; }; BBB55B131C8F8CBC0050DDA9 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = NO; CLANG_ENABLE_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = "iPhone Developer"; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = Tests/GlueKitTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER_BASE).Tests"; PRODUCT_NAME = "$(PROJECT_NAME)-Test"; SDKROOT = appletvos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; BBB55B141C8F8CBC0050DDA9 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = NO; CLANG_ENABLE_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = "iPhone Developer"; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = Tests/GlueKitTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER_BASE).Tests"; PRODUCT_NAME = "$(PROJECT_NAME)-Test"; SDKROOT = appletvos; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ BB351B041DB81E67005F083F /* Build configuration list for PBXNativeTarget "PerformanceTests" */ = { isa = XCConfigurationList; buildConfigurations = ( BB351B051DB81E67005F083F /* Debug */, BB351B061DB81E67005F083F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; BBB55AAC1C8F80020050DDA9 /* Build configuration list for PBXProject "GlueKit" */ = { isa = XCConfigurationList; buildConfigurations = ( BBB55AC41C8F80020050DDA9 /* Debug */, BBB55AC51C8F80020050DDA9 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; BBB55AC61C8F80020050DDA9 /* Build configuration list for PBXNativeTarget "iOS" */ = { isa = XCConfigurationList; buildConfigurations = ( BBB55AC71C8F80020050DDA9 /* Debug */, BBB55AC81C8F80020050DDA9 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; BBB55AC91C8F80020050DDA9 /* Build configuration list for PBXNativeTarget "iOS Tests" */ = { isa = XCConfigurationList; buildConfigurations = ( BBB55ACA1C8F80020050DDA9 /* Debug */, BBB55ACB1C8F80020050DDA9 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; BBB55AE51C8F88F20050DDA9 /* Build configuration list for PBXNativeTarget "macOS" */ = { isa = XCConfigurationList; buildConfigurations = ( BBB55AE61C8F88F20050DDA9 /* Debug */, BBB55AE71C8F88F20050DDA9 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; BBB55AE81C8F88F20050DDA9 /* Build configuration list for PBXNativeTarget "macOS Tests" */ = { isa = XCConfigurationList; buildConfigurations = ( BBB55AE91C8F88F20050DDA9 /* Debug */, BBB55AEA1C8F88F20050DDA9 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; BBB55AF61C8F8BE00050DDA9 /* Build configuration list for PBXNativeTarget "watchOS" */ = { isa = XCConfigurationList; buildConfigurations = ( BBB55AF71C8F8BE00050DDA9 /* Debug */, BBB55AF81C8F8BE00050DDA9 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; BBB55B0F1C8F8CBC0050DDA9 /* Build configuration list for PBXNativeTarget "tvOS" */ = { isa = XCConfigurationList; buildConfigurations = ( BBB55B101C8F8CBC0050DDA9 /* Debug */, BBB55B111C8F8CBC0050DDA9 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; BBB55B121C8F8CBC0050DDA9 /* Build configuration list for PBXNativeTarget "tvOS Tests" */ = { isa = XCConfigurationList; buildConfigurations = ( BBB55B131C8F8CBC0050DDA9 /* Debug */, BBB55B141C8F8CBC0050DDA9 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = BBB55AA91C8F80020050DDA9 /* Project object */; } ================================================ FILE: GlueKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: GlueKit.xcodeproj/project.xcworkspace/xcshareddata/GlueKit.xcscmblueprint ================================================ { "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "1C1227F33F1818028D5EE61FD15F57BEC36D44F4", "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { }, "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { "05D6826B0162543BD57EFC4EE6354EB49FD59438" : 9223372036854775807, "1C1227F33F1818028D5EE61FD15F57BEC36D44F4" : 9223372036854775807 }, "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "7F20ECAA-BD48-4077-9606-C54D512EB5F5", "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { "05D6826B0162543BD57EFC4EE6354EB49FD59438" : "BTree\/", "1C1227F33F1818028D5EE61FD15F57BEC36D44F4" : "GlueKit\/" }, "DVTSourceControlWorkspaceBlueprintNameKey" : "GlueKit", "DVTSourceControlWorkspaceBlueprintVersion" : 204, "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "GlueKit.xcodeproj", "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ { "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/lorentey\/BTree.git", "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "05D6826B0162543BD57EFC4EE6354EB49FD59438" }, { "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/lorentey\/GlueKit.git", "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "1C1227F33F1818028D5EE61FD15F57BEC36D44F4" } ] } ================================================ FILE: GlueKit.xcodeproj/xcshareddata/xcbaselines/BB351AFB1DB81E67005F083F.xcbaseline/EBF79DD4-EE66-4D33-B51C-EE37857B70A1.plist ================================================ classNames SignalPerformanceTests testChainedSendPerformance() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.52115 baselineIntegrationDisplayName 2016-10-19 23:39:50 testConcurrentSendPerformance() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 1.4352 baselineIntegrationDisplayName 2016-10-19 23:39:50 testSendPerformance() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.65624 baselineIntegrationDisplayName 2016-10-19 23:39:50 SignalSendTests testChainedSendPerformance() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.27853 baselineIntegrationDisplayName 2016-11-01 15:29:28 testConcurrentSendPerformance() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 2.2684 baselineIntegrationDisplayName 2016-11-01 15:29:28 test_send_toClosure() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.61261 baselineIntegrationDisplayName 2016-11-01 15:29:28 test_send_toSink() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.5974 baselineIntegrationDisplayName 2016-11-01 15:29:28 SignalSubscriptionTests test_subscribe_Closures() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.3053 baselineIntegrationDisplayName 2016-11-01 15:43:05 test_subscribe_EmptySink() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.20632 baselineIntegrationDisplayName 2016-11-01 15:43:05 test_subscribe_HardwiredMethodSink() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.21459 baselineIntegrationDisplayName 2016-11-01 15:43:05 test_subscribe_LocalMethodSink() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.32126 baselineIntegrationDisplayName 2016-11-01 15:29:28 test_subscribe_MethodSink() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.31883 baselineIntegrationDisplayName 2016-11-01 15:43:05 test_subscribe_PartiallyAppliedMethodSink() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.36757 baselineIntegrationDisplayName 2016-11-01 15:43:05 test_subscribe_RefCountingSink() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.22047 baselineIntegrationDisplayName 2016-11-01 15:43:05 SignalUnsubscriptionTests test_unsubscribe_emptySinks() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.18989 baselineIntegrationDisplayName 2016-11-01 15:29:28 ================================================ FILE: GlueKit.xcodeproj/xcshareddata/xcbaselines/BB351AFB1DB81E67005F083F.xcbaseline/Info.plist ================================================ runDestinationsByUUID EBF79DD4-EE66-4D33-B51C-EE37857B70A1 localComputer busSpeedInMHz 100 cpuCount 1 cpuKind Intel Core i7 cpuSpeedInMHz 2600 logicalCPUCoresPerPackage 8 modelCode MacBookPro10,1 physicalCPUCoresPerPackage 4 platformIdentifier com.apple.platform.macosx targetArchitecture x86_64 ================================================ FILE: GlueKit.xcodeproj/xcshareddata/xcbaselines/BBB55BE11C8FD1C60050DDA9.xcbaseline/DD663408-0BA9-46F9-868F-F1570927CA52.plist ================================================ classNames SignalPerformanceTests testChainedSendPerformance() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.53204 baselineIntegrationDisplayName 2016-10-19 23:23:14 testConcurrentSendPerformance() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 1.6244 baselineIntegrationDisplayName 2016-10-19 23:23:14 testSendPerformance() com.apple.XCTPerformanceMetric_WallClockTime baselineAverage 0.6672 baselineIntegrationDisplayName 2016-10-19 23:23:14 ================================================ FILE: GlueKit.xcodeproj/xcshareddata/xcbaselines/BBB55BE11C8FD1C60050DDA9.xcbaseline/Info.plist ================================================ runDestinationsByUUID DD663408-0BA9-46F9-868F-F1570927CA52 localComputer busSpeedInMHz 100 cpuCount 1 cpuKind Intel Core i7 cpuSpeedInMHz 2600 logicalCPUCoresPerPackage 8 modelCode MacBookPro10,1 physicalCPUCoresPerPackage 4 platformIdentifier com.apple.platform.macosx targetArchitecture x86_64 ================================================ FILE: GlueKit.xcodeproj/xcshareddata/xcschemes/GlueKit-PerformanceTests.xcscheme ================================================ ================================================ FILE: GlueKit.xcodeproj/xcshareddata/xcschemes/GlueKit-iOS.xcscheme ================================================ ================================================ FILE: GlueKit.xcodeproj/xcshareddata/xcschemes/GlueKit-macOS.xcscheme ================================================ ================================================ FILE: GlueKit.xcodeproj/xcshareddata/xcschemes/GlueKit-tvOS.xcscheme ================================================ ================================================ FILE: GlueKit.xcodeproj/xcshareddata/xcschemes/GlueKit-watchOS.xcscheme ================================================ ================================================ FILE: GlueKit.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: GlueKit.xcworkspace/xcshareddata/GlueKit.xcscmblueprint ================================================ { "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "1C1227F33F1818028D5EE61FD15F57BEC36D44F4", "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { }, "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { "05D6826B0162543BD57EFC4EE6354EB49FD59438" : 9223372036854775807, "FB48966E443D25259D3CA0BE6E56D2D7343B7F97" : 9223372036854775807, "1C1227F33F1818028D5EE61FD15F57BEC36D44F4" : 9223372036854775807 }, "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "46A01E16-332E-44AB-A9D5-63D415AD87CA", "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { "05D6826B0162543BD57EFC4EE6354EB49FD59438" : "GlueKit\/Carthage\/Checkouts\/BTree\/", "FB48966E443D25259D3CA0BE6E56D2D7343B7F97" : "GlueKit\/Carthage\/Checkouts\/SipHash\/", "1C1227F33F1818028D5EE61FD15F57BEC36D44F4" : "GlueKit\/" }, "DVTSourceControlWorkspaceBlueprintNameKey" : "GlueKit", "DVTSourceControlWorkspaceBlueprintVersion" : 204, "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "GlueKit.xcworkspace", "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ { "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/lorentey\/BTree.git", "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "05D6826B0162543BD57EFC4EE6354EB49FD59438" }, { "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/lorentey\/GlueKit.git", "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "1C1227F33F1818028D5EE61FD15F57BEC36D44F4" }, { "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/lorentey\/SipHash.git", "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "FB48966E443D25259D3CA0BE6E56D2D7343B7F97" } ] } ================================================ FILE: LICENSE.md ================================================ The MIT License (MIT) Copyright (c) 2015–2017 Károly Lőrentey Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Package.swift ================================================ // swift-tools-version:4.0 import PackageDescription let package = Package( name: "GlueKit", products: [ .library(name: "GlueKit", type: .dynamic, targets: ["GlueKit"]) ], dependencies: [ .package(url: "https://github.com/attaswift/SipHash", from: "1.2.0"), .package(url: "https://github.com/attaswift/BTree", from: "4.1.0") ], targets: [ .target(name: "GlueKit", dependencies: ["BTree", "SipHash"], path: "Sources"), .testTarget(name: "GlueKitTests", dependencies: ["GlueKit"], path: "Tests/GlueKitTests"), .testTarget(name: "PerformanceTests", dependencies: ["GlueKit"], path: "Tests/PerformanceTests") ], swiftLanguageVersions: [4] ) ================================================ FILE: README.md ================================================ # GlueKit [![Swift 3](https://img.shields.io/badge/Swift-3.0-blue.svg)](https://swift.org) [![License](https://img.shields.io/badge/licence-MIT-blue.svg)](https://github.com/attaswift/GlueKit/blob/master/LICENSE.md) [![Platform](https://img.shields.io/badge/platforms-macOS%20∙%20iOS%20∙%20watchOS%20∙%20tvOS-blue.svg)](https://developer.apple.com/platforms/) [![Build Status](https://travis-ci.org/attaswift/GlueKit.svg?branch=master)](https://travis-ci.org/attaswift/GlueKit) [![Code Coverage](https://codecov.io/github/attaswift/GlueKit/coverage.svg?branch=master)](https://codecov.io/github/attaswift/GlueKit?branch=master) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg)](https://github.com/Carthage/Carthage) [![CocoaPod Version](https://img.shields.io/cocoapods/v/GlueKit.svg)](http://cocoapods.org/pods/GlueKit) > :warning: **WARNING** :warning: This project is in a _prerelease_ state. There > is active work going on that will result in API changes that can/will break > code while things are finished. Use with caution. GlueKit is a Swift framework for creating observables and manipulating them in interesting and useful ways. It is called GlueKit because it lets you stick stuff together. GlueKit contains type-safe analogues for Cocoa's [Key-Value Coding][KVC] and [Key-Value Observing][KVO] subsystems, written in pure Swift. Besides providing the basic observation mechanism, GlueKit also supports full-blown *key path* observing, where a sequence of properties starting at a particular entity is observed at once. (E.g., you can observe a person's best friend's favorite color, which might change whenever the person gets a new best friend, or when the friend changes their mind about which color they like best.) [KVC]: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/KeyValueCoding/Articles/Overview.html [KVO]: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html (Note though that GlueKit's keys are functions so they aren't as easy to serialize as KVC's string-based keys and key paths. It is definitely possible to implement serializable type-safe keys in Swift; but it involves some boilerplate code that's better handled by code generation or core language enhancements such as property behaviors or improved reflection capabilities.) Like KVC/KVO, GlueKit supports observing not only individual values, but also collections like sets or arrays. This includes full support for key path observing, too -- e.g., you can observe a person's children's children as a single set. These observable collections report fine-grained incremental changes (e.g., "'foo' was inserted at index 5"), allowing you to efficiently react to their changes. Beyond key path observing, GlueKit also provides a rich set of transformations and combinations for observables as a more flexible and extensible Swift version of KVC's [collection operators][KVC ops]. E.g., given an observable array of integers, you can (efficiently!) observe the sum of its elements; you can filter it for elements that match a particular predicate; you can get an observable concatenation of it with another observable array; and you can do much more. [KVC ops]: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/KeyValueCoding/Articles/CollectionOperators.html You can use GlueKit's observable arrays to efficiently provide data to a `UITableView` or `UICollectionView`, including providing them with incremental changes for animated updates. This functionality is roughly equivalent to what [`NSFetchedResultsController`][NSFRC] does in Core Data. [NSFRC]: https://developer.apple.com/reference/coredata/nsfetchedresultscontroller GlueKit is written in pure Swift; it does not require the Objective-C runtime for its functionality. However, it does provide easy-to-use adapters that turn KVO-compatible key paths on NSObjects into GlueKit observables. GlueKit hasn't been officially released yet. Its API is still in flux, and it has wildly outdated and woefully incomplete documentation. However, the project is getting close to a feature set that would make a coherent 1.0 version; I expect to have a useful first release before the end of 2016. ## Presentation Károly gave a talk on GlueKit during [Functional Swift Conference 2016][FunSwift16] in Budapest. [Watch the video][funvideo] or [read the slides][slides]. [FunSwift16]: http://2016.funswiftconf.com [slides]: https://vellum.tech/assets/FunSwift2016%20-%20GlueKit.pdf [funvideo]: https://www.youtube.com/watch?v=98jsahDV4ts ## Installation ### CocoaPods If you use CocoaPods, you can start using GlueKit by including it as a dependency in your `Podfile`: ``` pod 'GlueKit', :git => 'https://github.com/attaswift/GlueKit.git' ``` (There are no official releases of GlueKit yet; the API is incomplete and very unstable for now.) ### Carthage For Carthage, add the following line to your `Cartfile`: ``` github "attaswift/GlueKit" "" ``` (You have to use a specific commit hash, because there are no official releases of GlueKit yet; the API is incomplete and very unstable for now.) ### Swift Package Manager For Swift Package Manager, add the following entry to the dependencies list inside your `Package.swift` file: ``` .Package(url: "https://github.com/attaswift/GlueKit.git", branch: master) ``` ### Standalone Development If you don't use CocoaPods, Carthage or SPM, you need to clone GlueKit, [BTree][btree] and [SipHash][siphash], and add references to their `xcodeproj` files to your project's workspace. You may put the clones wherever you like, but if you use Git for your app development, it is a good idea to set them up as submodules of your app's top-level Git repository. [btree]: https://github.com/attaswift/BTree [siphash]: https://github.com/attaswift/SipHash To link your application binary with GlueKit, just add `GlueKit.framework`, `BTree.framework` and `SipHash.framework` from the BTree project to the Embedded Binaries section of your app target's General page in Xcode. As long as the GlueKit and BTree project files are referenced in your workspace, these frameworks will be listed in the "Choose items to add" sheet that opens when you click on the "+" button of your target's Embedded Binaries list. There is no need to do any additional setup beyond adding the framework targets to Embedded Binaries. ### Working on GlueKit Itself If you want to do some work on GlueKit on its own, without embedding it in an application, simply clone this repo with the `--recursive` option, open `GlueKit.xcworkspace`, and start hacking. ``` git clone --recursive https://github.com/attaswift/GlueKit.git GlueKit open GlueKit/GlueKit.xcworkspace ``` ### Importing GlueKit Once you've made GlueKit available in your project, you need to import it at the top of each `.swift` file in which you want to use its features: ``` import GlueKit ``` ## Similar frameworks Some of GlueKit's constructs can be matched with those in discrete reactive frameworks, such as [ReactiveCocoa](https://github.com/ReactiveCocoa/ReactiveCocoa), [RxSwift](https://github.com/ReactiveX/RxSwift), [ReactKit](https://github.com/ReactKit/ReactKit), [Interstellar](https://github.com/JensRavens/Interstellar), and others. Sometimes GlueKit even uses the same name for the same concept. But often it doesn't (sorry). GlueKit concentrates on creating a useful model for observables, rather than trying to unify observable-like things with task-like things. GlueKit explicitly does not attempt to directly model networking operations (although a networking support library could certainly use GlueKit to implement some of its features). As such, GlueKit's source/signal/stream concept transmits simple values; it doesn't wrap them in `Event`s. I have several reasons I chose to create GlueKit instead of just using a better established and bug-free library: - I wanted to have some experience with reactive stuff, and you can learn a lot about a paradigm by trying to construct its foundations on your own. The idea is that I start simple and add things as I find I need them. I want to see if I arrive at the same problems and solutions as the Smart People who created the popular frameworks. Some common reactive patterns are not obviously right at first glance. - I wanted to experiment with reentrant observables, where an observer is allowed to trigger updates to the observable to which it's connected. I found no well-known implementation of Observable that gets this *just right*. - Building a library is a really fun diversion! ## Overview [The GlueKit Overview](https://github.com/attaswift/GlueKit/blob/master/Documentation/Overview.md) describes the basic concepts of GlueKit. ## Appetizer Let's say you're writing a bug tracker application that has a list of projects, each with its own set of issues. With GlueKit, you'd use `Variable`s to define your model's attributes and relationships: ```Swift class Project { let name: Variable let issues: ArrayVariable } class Account { let name: Variable let email: Variable } class Issue { let identifier: Variable let owner: Variable let isOpen: Variable let created: Variable } class Document { let accounts: ArrayVariable let projects: ArrayVariable } ``` You can use a `let observable: Variable` like you would a `var raw: Foo` property, except you need to write `observable.value` whenever you'd write `raw`: ```Swift // Raw Swift ===> // GlueKit var a = 42 ; let b = Variable(42) print("a = \(a)") ; print("b = \(b.value\)") a = 7 ; b.value = 7 ``` Given the model above, in Cocoa you could specify key paths for accessing various parts of the model from a `Document` instance. For example, to get the email addresses of all issue owners in one big unsorted array, you'd use the Cocoa key path `"projects.issues.owner.email"`. GlueKit is able to do this too, although it uses a specially constructed Swift closure to represent the key path: ```Swift let cocoaKeyPath: String = "projects.issues.owner.email" let swiftKeyPath: Document -> AnyObservableValue<[String]> = { document in document.projects.flatMap{$0.issues}.flatMap{$0.owner}.map{$0.email} } ``` (The type declarations are included to make it clear that GlueKit is fully type-safe. Swift's type inference is able to find these out automatically, so typically you'd omit specifying types in declarations like this.) The GlueKit syntax is certainly much more verbose, but in exchange it is typesafe, much more flexible, and also extensible. Plus, there is a visual difference between selecting a single value (`map`) or a collection of values (`flatMap`), which alerts you that using this key path might be more expensive than usual. (GlueKit's key paths are really just combinations of observables. `map` is a combinator that is used to build one-to-one key paths; there are many other interesting combinators available.) In Cocoa, you would get the current list of emails using KVC's accessor method. In GlueKit, if you give the key path a document instance, it returns an `AnyObservableValue` that has a `value` property that you can get. ```Swift let document: Document = ... let cocoaEmails: AnyObject? = document.valueForKeyPath(cocoaKeyPath) let swiftEmails: [String] = swiftKeyPath(document).value ``` In both cases, you get an array of strings. However, Cocoa returns it as an optional `AnyObject` that you'll need to unwrap and cast to the correct type yourself (you'll want to hold your nose while doing so). Boo! GlueKit knows what type the result is going to be, so it gives it to you straight. Yay! Neither Cocoa nor GlueKit allows you to update the value at the end of this key path; however, with Cocoa, you only find this out at runtime, while with GlueKit, you get a nice compiler error: ```Swift // Cocoa: Compiles fine, but oops, crash at runtime document.setValue("karoly@example.com", forKeyPath: cocoaKeyPath) // GlueKit/Swift: error: cannot assign to property: 'value' is a get-only property swiftKeyPath(document).value = "karoly@example.com" ``` You'll be happy to know that one-to-one key paths are assignable in both Cocoa and GlueKit: ```Swift let issue: Issue = ... /* Cocoa */ issue.setValue("karoly@example.com", forKeyPath: "owner.email") // OK /* GlueKit */ issue.owner.map{$0.email}.value = "karoly@example.com" // OK ``` (In GlueKit, you generally just use the observable combinators directly instead of creating key path entities. So we're going to do that from now on. Serializable type-safe key paths require additional work, which is better provided by a potentional future model object framework built on top of GlueKit.) More interestingly, you can ask to be notified whenever a key path changes its value. ```Swift // GlueKit let c = document.projects.flatMap{$0.issues}.flatMap{$0.owner}.map{$0.name}.subscribe { emails in print("Owners' email addresses are: \(emails)") } // Call c.disconnect() when you get bored of getting so many emails. // Cocoa class Foo { static let context: Int8 = 0 let document: Document init(document: Document) { self.document = document document.addObserver(self, forKeyPath: "projects.issues.owner.email", options: .New, context:&context) } deinit { document.removeObserver(self, forKeyPath: "projects.issues.owner.email", context: &context) } func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change change: [String : AnyObject]?, context context: UnsafeMutablePointer) { if context == &self.context { print("Owners' email addresses are: \(change[NSKeyValueChangeNewKey])) } else { super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context) } } } ``` Well, Cocoa is a mouthful, but people tend to wrap this up in their own abstractions. In both cases, a new set of emails is printed whenever the list of projects changes, or the list of issues belonging to any project changes, or the owner of any issue changes, or if the email address is changed on an individual account. To present a more down-to-earth example, let's say you want to create a view model for a project summary screen that displays various useful data about the currently selected project. GlueKit's observable combinators make it simple to put together data derived from our model objects. The resulting fields in the view model are themselves observable, and react to changes to any of their dependencies on their own. ```Swift class ProjectSummaryViewModel { let currentDocument: Variable = ... let currentAccount: Variable = ... let project: Variable = ... /// The name of the current project. var projectName: Updatable { return project.map { $0.name } } /// The number of issues (open and closed) in the current project. var isssueCount: AnyObservableValue { return project.selectCount { $0.issues } } /// The number of open issues in the current project. var openIssueCount: AnyObservableValue { return project.selectCount({ $0.issues }, filteredBy: { $0.isOpen }) } /// The ratio of open issues to all issues, in percentage points. var percentageOfOpenIssues: AnyObservableValue { // You can use the standard arithmetic operators to combine observables. return AnyObservableValue.constant(100) * openIssueCount / issueCount } /// The number of open issues assigned to the current account. var yourOpenIssues: AnyObservableValue { return project .selectCount({ $0.issues }, filteredBy: { $0.isOpen && $0.owner == self.currentAccount }) } /// The five most recently created issues assigned to the current account. var yourFiveMostRecentIssues: AnyObservableValue<[Issue]> { return project .selectFirstN(5, { $0.issues }, filteredBy: { $0.isOpen && $0.owner == currentAccount }), orderBy: { $0.created < $1.created }) } /// An observable version of NSLocale.currentLocale(). var currentLocale: AnyObservableValue { let center = NSNotificationCenter.defaultCenter() let localeSource = center .source(forName: NSCurrentLocaleDidChangeNotification) .map { _ in NSLocale.currentLocale() } return AnyObservableValue(getter: { NSLocale.currentLocale() }, futureValues: localeSource) } /// An observable localized string. var localizedIssueCountFormat: AnyObservableValue { return currentLocale.map { _ in return NSLocalizedString("%1$d of %2$d issues open (%3$d%%)", comment: "Summary of open issues in a project") } } /// An observable text for a label. var localizedIssueCountString: AnyObservableValue { return AnyObservableValue // Create an observable of tuples containing values of four observables .combine(localizedIssueCountFormat, issueCount, openIssueCount, percentageOfOpenIssues) // Then convert each tuple into a single localized string .map { format, all, open, percent in return String(format: format, open, all, percent) } } } ``` (Note that some of the operations above aren't implemented yet. Stay tuned!) Whenever the model is updated or another project or account is selected, the affected `Observable`s in the view model are recalculated accordingly, and their subscribers are notified with the updated values. GlueKit does this in a surprisingly efficient manner---for example, closing an issue in a project will simply decrement a counter inside `openIssueCount`; it won't recalculate the issue count from scratch. (Obviously, if the user switches to a new project, that change will trigger a recalculation of that project's issue counts from scratch.) Observables aren't actually calculating anything until and unless they have subscribers. Once you have this view model, the view controller can simply subscribe its observables to various labels displayed in the view hierarchy: ```Swift class ProjectSummaryViewController: UIViewController { private let visibleConnections = Connector() let viewModel: ProjectSummaryViewModel // ... override func viewWillAppear() { super.viewWillAppear() viewModel.projectName.values .subscribe { name in self.titleLabel.text = name } .putInto(visibleConnections) viewModel.localizedIssueCountString.values .subscribe { text in self.subtitleLabel.text = text } .putInto(visibleConnections) // etc. for the rest of the observables in the view model } override func viewDidDisappear() { super.viewDidDisappear() visibleConnections.disconnect() } } ``` Setting up the connections in `viewWillAppear` ensures that the view model's complex observer combinations are kept up to date only while the project summary is displayed on screen. The `projectName` property in `ProjectSummaryViewModel` is declared an `Updatable`, so you can modify its value. Doing that updates the name of the current project: ```Swift viewModel.projectName.value = "GlueKit" // Sets the current project's name via a key path print(viewModel.project.name.value) // Prints "GlueKit" ``` ================================================ FILE: Sources/Abstract.swift ================================================ // // Abstract.swift // GlueKit // // Created by Károly Lőrentey on 2016-08-22. // Copyright © 2015–2017 Károly Lőrentey. // internal func abstract() -> Never { fatalError("Unimplemented abstract method") } ================================================ FILE: Sources/AccumulatedSource.swift ================================================ // // AccumulatedSource.swift // GlueKit // // Created by Károly Lőrentey on 2017-04-23. // Copyright © 2015–2017 Károly Lőrentey. // extension SourceType { public func accumulated(_ initial: R, _ next: @escaping (R, Value) -> R) -> AnyObservableValue { return AccumulatedSource(self, initial, next).anyObservableValue } public func counted() -> AnyObservableValue { return accumulated(0) { value, _ in value + 1 } } } private class AccumulatedSource: _BaseObservableValue where S: SourceType { let source: S let next: (Value, S.Value) -> Value var _value: Value struct Sink: UniqueOwnedSink { typealias Owner = AccumulatedSource unowned(unsafe) let owner: Owner func receive(_ value: S.Value) { owner.beginTransaction() let old = owner._value let new = owner.next(owner._value, value) owner._value = new owner.sendChange(ValueChange(from: old, to: new)) owner.endTransaction() } } init(_ source: S, _ initial: Value, _ next: @escaping (Value, S.Value) -> Value) { self.source = source self.next = next self._value = initial super.init() source.add(Sink(owner: self)) } deinit { source.remove(Sink(owner: self)) } override var value: Value { return _value } } ================================================ FILE: Sources/ArrayBasedTableViewDataSource.swift ================================================ // // ArrayBasedTableViewDataSource.swift // GlueKit // // Created by Károly Lőrentey on 2017-04-24. // Copyright © 2015–2017 Károly Lőrentey. // #if os(iOS) import UIKit extension IndexSet { func toIndexPaths(section: Int = 0) -> [IndexPath] { return self.map { IndexPath(row: $0, section: section) } } } public func <-- (target: DependentArray, source: Source) where Source.Element == Element { target.origin = source.anyObservableArray } public class DependentArray { private var _itemsRef = Variable>(.constant([])) private var _items: AnyObservableArray public let changes: AnySource> public init() { self._items = _itemsRef.unpacked() self.changes = _items.changes } public var origin: AnyObservableArray { get { return _items } set { _itemsRef.value = newValue } } } public class ArrayBasedTableViewDataSource: NSObject, UITableViewDataSource { public let tableView: UITableView public let reuseIdentifier: String public let cellLoader: (Cell, Item) -> Void public let items = DependentArray() private struct Sink: UniqueOwnedSink { typealias Owner = ArrayBasedTableViewDataSource unowned(unsafe) let owner: Owner func receive(_ change: ArrayChange) { owner.receive(change) } } public init(tableView: UITableView, reuseIdentifier: String, cellLoader: @escaping (Cell, Item) -> Void) { self.tableView = tableView self.reuseIdentifier = reuseIdentifier self.cellLoader = cellLoader super.init() self.items.changes.add(Sink(owner: self)) tableView.dataSource = self } deinit { self.items.changes.remove(Sink(owner: self)) } public convenience init(tableView: UITableView, reuseIdentifier: String) { self.init(tableView: tableView, reuseIdentifier: reuseIdentifier, cellLoader: { cell, item in cell.textLabel!.text = "\(item)" }) } private func receive(_ change: ArrayChange) { guard !change.isEmpty else { return } let batch = change.separated() //print("Batch: count=\(change.initialCount)/\(change.finalCount), deleted=\(Array(batch.deleted)), inserted=\(Array(batch.inserted)), moved=\(batch.moved)") assert(batch.change.finalCount == items.origin.count) tableView.beginUpdates() tableView.deleteRows(at: batch.deleted.toIndexPaths(), with: .fade) tableView.insertRows(at: batch.inserted.toIndexPaths(), with: .fade) for (from, to) in batch.moved { tableView.moveRow(at: IndexPath(row: from, section: 0), to: IndexPath(row: to, section: 0)) if let cell = tableView.cellForRow(at: IndexPath(row: from, section: 0)) as? Cell { cellLoader(cell, items.origin[to]) } } tableView.endUpdates() } public func numberOfSections(in tableView: UITableView) -> Int { return 1 } public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { guard section == 0 else { return 0 } return items.origin.count } public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { precondition(indexPath.section == 0) let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) as! Cell cellLoader(cell, items.origin[indexPath.row]) return cell } public func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return nil } public func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { return nil } public func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { return false } public func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool { return false } } #endif ================================================ FILE: Sources/ArrayChange.swift ================================================ // // ArrayChange.swift // GlueKit // // Created by Károly Lőrentey on 2015-12-10. // Copyright © 2015–2017 Károly Lőrentey. // /// Describes a single modification of an array. The modification can be an insertion, a removal, a replacement or /// a generic range replacement. All indices are understood to be based in the original array, before this modification /// has taken place. /// /// Every modification can be converted to a range replacement by using the `range` and `elements` properties. /// There is an initializer to convert a pair of `range` and `elements` back into a modification. public enum ArrayModification { /// The insertion of a single element at the specified position. case insert(Element, at: Int) /// The removal of a single element at the specified position. case remove(Element, at: Int) /// The replacement of a single element at the specified position with the specified new element. case replace(Element, at: Int, with: Element) /// The replacement of the specified contiguous range of elements with the specified new list of elements. /// The count of the range need not equal the replacement element count. /// /// Note that all other modification cases have an equivalent range replacement. /// We chose to keep them separate only because they are more expressive that way. case replaceSlice([Element], at: Int, with: [Element]) /// Convert a contiguous replacement range and a replacement list of elements into a modification. public init?(replacing old: [Element], at index: Int, with new: [Element]) { switch (old.count, new.count) { case (0, 0): return nil case (0, 1): self = .insert(new[0], at: index) case (1, 0): self = .remove(old[0], at: index) case (1, 1): self = .replace(old[0], at: index, with: new[0]) default: self = .replaceSlice(old, at: index, with: new) } } /// The effect of this modification on the element count of the array. var deltaCount: Int { switch self { case .insert(_, at: _): return 1 case .remove(_, at: _): return -1 case .replace(_, at: _, with: _): return 0 case .replaceSlice(let old, at: _, with: let new): return new.count - old.count } } public var startIndex: Int { switch self { case .insert(_, at: let index): return index case .remove(_, at: let index): return index case .replace(_, at: let index, with: _): return index case .replaceSlice(_, at: let index, with: _): return index } } public var inputCount: Int { switch self { case .insert(_, at: _): return 0 case .remove(_, at: _): return 1 case .replace(_, at: _, with: _): return 1 case .replaceSlice(let old, at: _, with: _): return old.count } } public var outputCount: Int { switch self { case .insert(_, at: _): return 1 case .remove(_, at: _): return 0 case .replace(_, at: _, with: _): return 1 case .replaceSlice(_, at: _, with: let new): return new.count } } /// The range (in the original array) that this modification replaces, when converted into a range modification. public var inputRange: CountableRange { return startIndex ..< startIndex + inputCount } /// The range (in the resulting array) that this modification changed. public var outputRange: CountableRange { return startIndex ..< startIndex + outputCount } /// The replacement elements that get inserted by this modification. public var newElements: [Element] { switch self { case .insert(let e, at: _): return [e] case .remove(_, at: _): return [] case .replace(_, at: _, with: let e): return [e] case .replaceSlice(_, at: _, with: let es): return es } } /// The original elements that this modification removes/changes. public var oldElements: [Element] { switch self { case .insert(_, at: _): return [] case .remove(let e, at: _): return [e] case .replace(let e, at: _, with: _): return [e] case .replaceSlice(let es, at: _, with: _): return es } } var reversed: ArrayModification { switch self { case .insert(let new, at: let index): return .remove(new, at: index) case .remove(let old, at: let index): return .insert(old, at: index) case .replace(let old, at: let index, with: let new): return .replace(new, at: index, with: old) case .replaceSlice(let old, at: let index, with: let new): return .replaceSlice(new, at: index, with: old) } } /// Try to merge `mod` into this modification and return the result. func merged(with mod: ArrayModification) -> ArrayModificationMergeResult { let start1 = self.startIndex let start2 = mod.startIndex let outputCount1 = self.outputCount let outputEnd1 = start1 + outputCount1 let inputCount2 = mod.inputCount let inputEnd2 = start2 + inputCount2 if outputEnd1 < start2 { // New range affects indices greater than our range return .disjunctOrderedAfter } if inputEnd2 < start1 { // New range affects indices earlier than our range return .disjunctOrderedBefore } // There is some overlap or the ranges are touching each other. let combinedStart = min(start1, start2) let oldElements2 = mod.oldElements var combinedOld = self.oldElements if start2 < start1 { combinedOld.insert(contentsOf: oldElements2[0 ..< start1 - start2], at: 0) } let c = inputEnd2 - outputEnd1 if c > 0 { combinedOld.append(contentsOf: oldElements2.suffix(c)) } var combinedNew = self.newElements combinedNew.replaceSubrange(max(0, start2 - start1) ..< min(outputCount1, inputEnd2 - start1), with: mod.newElements) if let mod = ArrayModification(replacing: combinedOld, at: combinedStart, with: combinedNew) { return .collapsedTo(mod) } return .collapsedToNoChange } /// Transform each element in this modification using the function `transform`. public func map(_ transform: (Element) -> Result) -> ArrayModification { switch self { case .insert(let new, at: let i): return .insert(transform(new), at: i) case .remove(let old, at: let i): return .remove(transform(old), at: i) case .replace(let old, at: let i, with: let new): return .replace(transform(old), at: i, with: transform(new)) case .replaceSlice(let old, at: let i, with: let new): return .replaceSlice(old.map(transform), at: i, with: new.map(transform)) } } /// Call `body` on each old element in this modification. public func forEachOldElement(_ body: (Element) -> Void) { switch self { case .insert(_, at: _): break case .remove(let old, at: _): body(old) case .replace(let old, at: _, with: _): body(old) case .replaceSlice(let old, at: _, with: _): old.forEach(body) } } /// Call `body` on each new element in this modification. public func forEachNewElement(_ body: (Element) -> Void) { switch self { case .insert(let new, at: _): body(new) case .remove(_, at: _): break case .replace(_, at: _, with: let new): body(new) case .replaceSlice(_, at: _, with: let new): new.forEach(body) } } /// Add the specified delta to all indices in this modification. public func shift(_ delta: Int) -> ArrayModification { switch self { case .insert(let new, at: let i): return .insert(new, at: i + delta) case .remove(let old, at: let i): return .remove(old, at: i + delta) case .replace(let old, at: let i, with: let new): return .replace(old, at: i + delta, with: new) case .replaceSlice(let old, at: let i, with: let new): return .replaceSlice(old, at: i + delta, with: new) } } } extension ArrayModification where Element: Equatable { /// Returns an array of modifications that perform the same update as this one, except all cases are removed where /// an element is replaced by a value that is equal to it. public func removingEqualChanges() -> [ArrayModification] { switch self { case .insert(_, at: _): return [self] case .remove(_, at: _): return [self] case .replace(let old, at: _, with: let new): return old == new ? [] : [self] case .replaceSlice(let old, at: let index, with: let new): var result: [ArrayModification] = [] var start = 0 for i in 0 ..< min(old.count, new.count) { if old[i] == new[i] { if start < i { if let mod = ArrayModification(replacing: Array(old[start ..< i]), at: index + start, with: Array(new[start ..< i])) { result.append(mod) } } start = i + 1 } } if old.count != new.count || start < old.count { if let mod = ArrayModification(replacing: Array(old.suffix(from: start)), at: index + start, with: Array(new.suffix(from: start))) { result.append(mod) } } return result } } /// Returns true iff the result array is equal to the original, i.e. if it doesn't change anything, or only replaces /// values with equal values. public var isIdentity: Bool { switch self { case .insert(_, at: _): return false case .remove(_, at: _): return false case .replace(let old, at: _, with: let new): return old == new case .replaceSlice(let old, at: _, with: let new): return old == new } } } /// The result of an attempt at merging two array modifications. internal enum ArrayModificationMergeResult { /// The modifications are disjunct, and the new modification changes indexes below the old. case disjunctOrderedBefore /// The modifications are disjunct, and the new modification changes indexes above the old. case disjunctOrderedAfter /// The modifications are intersecting, and cancel each other out. case collapsedToNoChange /// The modifications are intersecting, and merge to the specified new modification. case collapsedTo(ArrayModification) } extension ArrayModification: CustomStringConvertible, CustomDebugStringConvertible { public var description: String { switch self { case .insert(let new, at: let i): return ".insert(\(new), at: \(i))" case .remove(let old, at: let i): return ".remove(\(old), at: \(i))" case .replace(let old, at: let i, with: let new): return ".replace(\(old), at: \(i), with: \(new))" case .replaceSlice(let old, at: let i, with: let new): let oldString = "[\(old.map { "\($0)" }.joined(separator: ", "))]" let newString = "[\(new.map { "\($0)" }.joined(separator: ", "))]" return ".replaceSlice(\(oldString), at: \(i), with: \(newString))" } } public var debugDescription: String { switch self { case .insert(let new, at: let i): return ".insert(\(String(reflecting: new)), at: \(i))" case .remove(let old, at: let i): return ".remove(\(String(reflecting: old)), at: \(i))" case .replace(let old, at: let i, with: let new): return ".replace(\(String(reflecting: old)), at: \(i), with: \(String(reflecting: new)))" case .replaceSlice(let old, at: let i, with: let new): let oldString = "[\(old.map { String(reflecting: $0) }.joined(separator: ", "))]" let newString = "[\(new.map { String(reflecting: $0) }.joined(separator: ", "))]" return ".replaceSlice(\(oldString), at: \(i), with: \(newString))" } } } extension ArrayModification: CustomPlaygroundQuickLookable { public var customPlaygroundQuickLook: PlaygroundQuickLook { return .text(description) } } extension RangeReplaceableCollection where Index == Int { /// Apply `modification` to this array in place. public mutating func apply(_ modification: ArrayModification) { switch modification { case .insert(let element, at: let index): self.insert(element, at: index) case .remove(_, at: let index): self.remove(at: index) case .replace(_, at: let index, with: let new): self.replaceSubrange(index ..< index + 1, with: [new]) case .replaceSlice(let old, at: let index, with: let new): self.replaceSubrange(index ..< index + old.count, with: new) } } } public func ==(a: ArrayModification, b: ArrayModification) -> Bool { return a.startIndex == b.startIndex && a.inputCount == b.inputCount && a.outputCount == b.outputCount && a.oldElements == b.oldElements && a.newElements == b.newElements } public func !=(a: ArrayModification, b: ArrayModification) -> Bool { return !(a == b) } //MARK: ArrayChange /// ArrayChange describes a series of one or more modifications to an array. Each modification is the replacement of /// a contiguous range of elements with another set of elements (see `ArrayModification`). /// /// You can efficiently merge array changes together forming a single change, without constructing the whole array /// in between. You can also transform values contained array changes using any transform function. /// /// Array changes may only be applied on arrays that have the same number of elements as the original array. /// /// - SeeAlso: ArrayModification, AnyObservableArray, ArrayVariable public struct ArrayChange: ChangeType { public typealias Value = [Element] /// The expected initial count of elements in the array on the input of this change. public private(set) var initialCount: Int /// The expected change in the count of elements in the array as a result of this change. public var deltaCount: Int { return modifications.reduce(0) { s, mod in s + mod.deltaCount } } public var countChange: ValueChange { return .init(from: initialCount, to: finalCount) } public var finalCount: Int { return initialCount + deltaCount } /// The sequence of independent modifications to apply, in order of the start indexes of their ranges. /// All indices are understood to be in the array resulting from the original array by applying all /// earlier modifications. (So you can simply loop over the modifications and apply them one by one.) public private(set) var modifications: [ArrayModification] = [] public init(initialCount: Int) { self.initialCount = initialCount self.modifications = [] } internal init(initialCount: Int, modifications: [ArrayModification]) { self.initialCount = initialCount self.modifications = modifications } /// Initializes a change with `count` as the expected initial count and consisting of `modification`. public init(initialCount: Int, modification: ArrayModification) { self.initialCount = initialCount self.modifications = [modification] } /// Initializes a change that simply replaces all elements in `previousValue` with the ones in `newValue`. public init(from previousValue: Value, to newValue: Value) { // Elements aren't necessarily equatable here, so this is the best we can do. if let mod = ArrayModification(replacing: previousValue, at: 0, with: newValue) { self.init(initialCount: previousValue.count, modification: mod) } else { self.init(initialCount: previousValue.count) } } /// Returns true if this change contains no actual changes to the array. /// This can happen if a series of merged changes cancel each other out---such as the insertion of an element /// and the subsequent removal of the same. public var isEmpty: Bool { return modifications.isEmpty } public mutating func add(_ new: ArrayModification) { var pos = modifications.count - 1 var m = new while pos >= 0 { let old = modifications[pos] let res = old.merged(with: m) switch res { case .disjunctOrderedAfter: modifications.insert(m, at: pos + 1) return case .disjunctOrderedBefore: modifications[pos] = old.shift(m.deltaCount) pos -= 1 continue case .collapsedToNoChange: modifications.remove(at: pos) return case .collapsedTo(let merged): modifications.remove(at: pos) m = merged pos -= 1 continue } } modifications.insert(m, at: 0) } public func apply(on value: inout Array) { precondition(value.count == initialCount) value.apply(self) } /// Merge `other` into this change, modifying it in place. /// `other.initialCount` must be equal to `self.finalCount`, or the merge will report a fatal error. public mutating func merge(with other: ArrayChange) { precondition(finalCount == other.initialCount) for m in other.modifications { add(m) } } /// Returns a new change that contains all changes in this change plus all changes in `other`. /// `other.initialCount` must be equal to `self.finalCount`, or the merge will report a fatal error. public func merged(with other: ArrayChange) -> ArrayChange { var result = self result.merge(with: other) return result } public func reversed() -> ArrayChange { var result = ArrayChange(initialCount: self.finalCount) var delta = 0 for mod in self.modifications { result.add(mod.reversed.shift(delta)) delta -= mod.deltaCount } return result } /// Transform all element values contained in this change using the `transform` function. public func map(_ transform: (Element) -> Result) -> ArrayChange { return ArrayChange(initialCount: initialCount, modifications: modifications.map { $0.map(transform) }) } /// Call `body` on each element value that is removed by this change. public func forEachOld(_ body: (Element) -> Void) { modifications.forEach { $0.forEachOldElement(body) } } /// Call `body` on each element value that is added by this change. public func forEachNew(_ body: (Element) -> Void) { modifications.forEach { $0.forEachNewElement(body) } } /// Convert this change so that it modifies a range of items in a larger array. /// /// Modifications contained in the result will be the same as in this change, except they will /// apply on the range `startIndex ..< startIndex + self.initialCount` in the wider array. /// /// - Parameter startIndex: The start index of the range to rebase this change into. /// - Parameter count: The element count of the wider array to rebase this change into. /// - Returns: A new change that applies the same modifications on a range inside a wider array. public func widen(startIndex: Int, initialCount: Int) -> ArrayChange { precondition(startIndex + self.initialCount <= initialCount) if startIndex > 0 { let mods = modifications.map { $0.shift(startIndex) } return ArrayChange(initialCount: initialCount, modifications: mods) } else { return ArrayChange(initialCount: initialCount, modifications: modifications) } } } extension ArrayChange: CustomStringConvertible { public var description: String { let type = String(describing: ArrayChange.self) let c = modifications.count return "\(type) initialCount: \(initialCount), \(c) modifications" } } extension ArrayChange: CustomDebugStringConvertible { public var debugDescription: String { let type = String(reflecting: ArrayChange.self) let c = modifications.count return "\(type) initialCount: \(initialCount), \(c) modifications" } } extension ArrayChange: CustomReflectable { public var customMirror: Mirror { return Mirror(self, children: ["initialCount": initialCount, "modifications": modifications], displayStyle: .struct) } } extension ArrayChange where Element: Equatable { public func removingEqualChanges() -> ArrayChange { return ArrayChange(initialCount: initialCount, modifications: modifications.flatMap { $0.removingEqualChanges() }) } } public func ==(a: ArrayChange, b: ArrayChange) -> Bool { return (a.initialCount == b.initialCount && a.modifications.elementsEqual(b.modifications, by: ==)) } public func !=(a: ArrayChange, b: ArrayChange) -> Bool { return !(a == b) } extension RangeReplaceableCollection where Index == Int, IndexDistance == Int { /// Apply `change` to this array. The count of self must be the same as the initial count of `change`, or /// the operation will report a fatal error. public mutating func apply(_ change: ArrayChange) { precondition(self.count == change.initialCount) for modification in change.modifications { self.apply(modification) } } } ================================================ FILE: Sources/ArrayChangeSeparation.swift ================================================ // // ArrayChangeSeparation.swift // GlueKit // // Created by Károly Lőrentey on 2016-08-27. // Copyright © 2015–2017 Károly Lőrentey. // import Foundation // For IndexSet private func separationError() -> Never { fatalError("Changes in arrays with duplicate elements cannot be separated") } extension ArrayChange { /// Return the set of indices at which elements will be deleted or updated from the array when this change is applied. /// /// The returned indices are relative to the original state of the array. /// /// This is intended to be given to a `UITableView` inside a `beginUpdates` block or a `UICollectionView` inside /// a `performBatchUpdates` block. /// /// - SeeAlso: `separated()` for a more thorough analysis of the change, including detection of elements that were moved. public var deletedIndices: IndexSet { var result = IndexSet() var delta = 0 for modification in modifications { switch modification { case .insert(_, at: _): break case .remove(_, at: let index): result.insert(index - delta) case .replace(_, at: let index, with: _): result.insert(index - delta) case .replaceSlice(let old, let index, with: _): result.insert(integersIn: index - delta ..< index + old.count - delta) } delta += modification.deltaCount } return result } /// Return the set of indices at which elements will be inserted or updated in the array when this change is applied. /// /// The returned indices assume deletions were already done. /// /// This is intended to be given to a `UITableView` inside a `beginUpdates` block or a `UICollectionView` inside /// a `performBatchUpdates` block. /// /// - SeeAlso: `separated()` for a more thorough analysis of the change, including detection of elements that were moved. public var insertedIndices: IndexSet { var result = IndexSet() for modification in modifications { switch modification { case .insert(_, at: let index): result.insert(index) case .remove(_, at: _): break case .replace(_, at: let index, with: _): result.insert(index) case .replaceSlice(_, at: let index, with: let new): result.insert(integersIn: index ..< index + new.count) } } return result } } extension ArrayChange where Element: Hashable { /// Separates this change into components that can be directly fed into a `UITableView` or a `UICollectionView` as a batch update. /// /// - Requires: The array must not contain duplicate elements. public func separated() -> SeparatedArrayChange { return SeparatedArrayChange(self) } } public struct SeparatedArrayChange { // The original change. public var change: ArrayChange /// The indices that are to be deleted. public var deleted = IndexSet() /// The indices that are inserted. public var inserted = IndexSet() /// The old and new indices of elements that are to be moved. /// (This includes elements that need to be refreshed.) public var moved: [(from: Int, to: Int)] = [] init(_ change: ArrayChange) { self.change = change var deletedElements: [Element: Int] = [:] var insertedElements: [Element: Int] = [:] var delta = 0 for modification in change.modifications { switch modification { case .insert(let new, at: let index): guard insertedElements.updateValue(index, forKey: new) == nil else { separationError() } case .remove(let old, at: let index): guard deletedElements.updateValue(index - delta, forKey: old) == nil else { separationError() } case .replace(let old, at: let index, with: let new): guard deletedElements.updateValue(index - delta, forKey: old) == nil else { separationError() } guard insertedElements.updateValue(index, forKey: new) == nil else { separationError() } case .replaceSlice(let old, at: let index, with: let new): for i in 0 ..< min(old.count, new.count) { guard deletedElements.updateValue(index + i - delta, forKey: old[i]) == nil else { separationError() } guard insertedElements.updateValue(index + i, forKey: new[i]) == nil else { separationError() } } if old.count < new.count { for i in old.count ..< new.count { guard insertedElements.updateValue(index + i, forKey: new[i]) == nil else { separationError() } } } else if old.count > new.count { for i in new.count ..< old.count { guard deletedElements.updateValue(index + i - delta, forKey: old[i]) == nil else { separationError() } } } } delta += modification.deltaCount } for (element, from) in deletedElements { if let to = insertedElements[element] { moved.append((from, to)) deletedElements.removeValue(forKey: element) insertedElements.removeValue(forKey: element) } } self.deleted = IndexSet(deletedElements.values) self.inserted = IndexSet(insertedElements.values) } } ================================================ FILE: Sources/ArrayConcatenation.swift ================================================ // // Concatenation.swift // GlueKit // // Created by Károly Lőrentey on 2016-08-22. // Copyright © 2015–2017 Károly Lőrentey. // extension ObservableArrayType { public func concatenate(with other: A) -> AnyObservableArray where A.Element == Element { return ArrayConcatenation(first: self, second: other).anyObservableArray } } public func +(a: A, b: B) -> AnyObservableArray where A.Element == B.Element { return a.concatenate(with: b) } final class ArrayConcatenation: _BaseObservableArray where First.Element == Second.Element { typealias Element = First.Element typealias Change = ArrayChange private struct FirstSink: UniqueOwnedSink { typealias Owner = ArrayConcatenation unowned(unsafe) let owner: Owner func receive(_ update: ArrayUpdate) { owner.applyFirst(update) } } private struct SecondSink: UniqueOwnedSink { typealias Owner = ArrayConcatenation unowned(unsafe) let owner: Owner func receive(_ update: ArrayUpdate) { owner.applySecond(update) } } let first: First let second: Second private var firstCount = -1 private var secondCount = -1 init(first: First, second: Second) { self.first = first self.second = second } override var isBuffered: Bool { return false } override subscript(index: Int) -> Element { let c = first.count if index < c { return first[index] } return second[index - c] } override subscript(bounds: Range) -> ArraySlice { let c = first.count if bounds.upperBound <= c { return first[bounds] } if bounds.lowerBound >= c { return second[bounds.lowerBound - c ..< bounds.upperBound - c] } return ArraySlice(first[bounds.lowerBound ..< c] + second[0 ..< bounds.upperBound - c]) } override var value: [Element] { return first.value + second.value } override var count: Int { return first.count + second.count } override func activate() { firstCount = first.count secondCount = second.count first.updates.add(FirstSink(owner: self)) second.updates.add(SecondSink(owner: self)) } override func deactivate() { first.updates.remove(FirstSink(owner: self)) second.updates.remove(SecondSink(owner: self)) firstCount = -1 secondCount = -1 } func applyFirst(_ update: ArrayUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): precondition(self.firstCount == change.initialCount) firstCount = change.finalCount sendChange(change.widen(startIndex: 0, initialCount: change.initialCount + self.secondCount)) case .endTransaction: endTransaction() } } func applySecond(_ update: ArrayUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): precondition(self.secondCount == change.initialCount) secondCount = change.finalCount sendChange(change.widen(startIndex: self.firstCount, initialCount: self.firstCount + change.initialCount)) case .endTransaction: endTransaction() } } } ================================================ FILE: Sources/ArrayFilteringIndexmap.swift ================================================ // // ArrayFilteringIndexmap.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-07. // Copyright © 2015–2017 Károly Lőrentey. // import BTree internal struct ArrayFilteringIndexmap { let isIncluded: (Element) -> Bool var matchingIndices = SortedSet() init(initialValues values: [Element], isIncluded: @escaping (Element) -> Bool) { self.isIncluded = isIncluded for index in values.indices { if isIncluded(values[index]) { matchingIndices.insert(index) } } } mutating func apply(_ change: ArrayChange) -> ArrayChange { var filteredChange = ArrayChange(initialCount: matchingIndices.count) for mod in change.modifications { switch mod { case .insert(let element, at: let index): matchingIndices.shift(startingAt: index, by: 1) if isIncluded(element) { matchingIndices.insert(index) filteredChange.add(.insert(element, at: matchingIndices.offset(of: index)!)) } case .remove(let element, at: let index): if let filteredIndex = matchingIndices.offset(of: index) { filteredChange.add(.remove(element, at: filteredIndex)) } matchingIndices.shift(startingAt: index + 1, by: -1) case .replace(let old, at: let index, with: let new): switch (matchingIndices.offset(of: index), isIncluded(new)) { case (.some(let offset), true): filteredChange.add(.replace(old, at: offset, with: new)) case (.none, true): matchingIndices.insert(index) filteredChange.add(.insert(new, at: matchingIndices.offset(of: index)!)) case (.some(let offset), false): matchingIndices.remove(index) filteredChange.add(.remove(old, at: offset)) case (.none, false): // Do nothing break } case .replaceSlice(let old, at: let index, with: let new): let filteredIndex = matchingIndices.prefix(upTo: index).count let filteredOld = matchingIndices.intersection(elementsIn: index ..< index + old.count).map { old[$0 - index] } var filteredNew: [Element] = [] matchingIndices.subtract(elementsIn: index ..< index + old.count) matchingIndices.shift(startingAt: index + old.count, by: new.count - old.count) for i in 0 ..< new.count { if isIncluded(new[i]) { matchingIndices.insert(index + i) filteredNew.append(new[i]) } } if let mod = ArrayModification(replacing: filteredOld, at: filteredIndex, with: filteredNew) { filteredChange.add(mod) } } } return filteredChange } mutating func insert(_ index: Int) -> Int? { guard !matchingIndices.contains(index) else { return nil } matchingIndices.insert(index) return matchingIndices.offset(of: index)! } mutating func remove(_ index: Int) -> Int? { guard let filteredIndex = matchingIndices.offset(of: index) else { return nil } matchingIndices.remove(index) return filteredIndex } } ================================================ FILE: Sources/ArrayFilteringOnObservableBool.swift ================================================ // // ArrayFilteringOnObservableBool.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-07. // Copyright © 2015–2017 Károly Lőrentey. // extension ObservableArrayType { public func filter(_ isIncluded: @escaping (Element) -> Test) -> AnyObservableArray where Test.Value == Bool { return ArrayFilteringOnObservableBool(parent: self, isIncluded: isIncluded).anyObservableArray } public func filter(_ isIncluded: Predicate) -> AnyObservableArray where Predicate.Value == (Element) -> Bool { return self.filter(isIncluded.map { predicate -> Optional<(Element) -> Bool> in predicate }) } public func filter(_ isIncluded: Predicate) -> AnyObservableArray where Predicate.Value == Optional<(Element) -> Bool> { let reference: AnyObservableValue> = isIncluded.map { predicate in if let predicate: (Element) -> Bool = predicate { return self.filter(predicate).anyObservableArray } else { return self.anyObservableArray } } return reference.unpacked() } } private class ArrayFilteringOnObservableBool: _BaseObservableArray where Test.Value == Bool { typealias Element = Parent.Element typealias Change = ArrayChange private struct ParentSink: UniqueOwnedSink { typealias Owner = ArrayFilteringOnObservableBool unowned(unsafe) let owner: Owner func receive(_ update: ArrayUpdate) { owner.applyParentUpdate(update) } } private final class FieldSink: SinkType, RefListElement { typealias Owner = ArrayFilteringOnObservableBool unowned let owner: Owner let field: Test var refListLink = RefListLink() init(owner: Owner, field: Test) { self.owner = owner self.field = field field.add(self) } func disconnect() { field.remove(self) } func receive(_ update: ValueUpdate) { owner.applyFieldUpdate(update, from: self) } } private let parent: Parent private let isIncluded: (Element) -> Test private var indexMapping: ArrayFilteringIndexmap private var elementConnections = RefList() init(parent: Parent, isIncluded: @escaping (Element) -> Test) { self.parent = parent self.isIncluded = isIncluded let elements = parent.value self.indexMapping = ArrayFilteringIndexmap(initialValues: elements, isIncluded: { isIncluded($0).value }) super.init() parent.updates.add(ParentSink(owner: self)) self.elementConnections = RefList(elements.lazy.map { FieldSink(owner: self, field: isIncluded($0)) }) } deinit { parent.updates.remove(ParentSink(owner: self)) self.elementConnections.forEach { $0.disconnect() } } private func applyParentUpdate(_ update: ArrayUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): for mod in change.modifications { let inputRange = mod.inputRange inputRange.forEach { elementConnections[$0].disconnect() } elementConnections.replaceSubrange(inputRange, with: mod.newElements.map { FieldSink(owner: self, field: isIncluded($0)) }) } let filteredChange = self.indexMapping.apply(change) if !filteredChange.isEmpty { sendChange(filteredChange) } case .endTransaction: endTransaction() } } private func applyFieldUpdate(_ update: ValueUpdate, from sink: FieldSink) { switch update { case .beginTransaction: beginTransaction() case .change(let change): if change.old == change.new { return } let index = elementConnections.index(of: sink)! let c = indexMapping.matchingIndices.count if change.new, let filteredIndex = indexMapping.insert(index) { sendChange(ArrayChange(initialCount: c, modification: .insert(parent[index], at: filteredIndex))) } else if !change.new, let filteredIndex = indexMapping.remove(index) { sendChange(ArrayChange(initialCount: c, modification: .remove(parent[index], at: filteredIndex))) } case .endTransaction: endTransaction() } } override var isBuffered: Bool { return false } override subscript(index: Int) -> Element { return parent[indexMapping.matchingIndices[index]] } override subscript(bounds: Range) -> ArraySlice { precondition(0 <= bounds.lowerBound && bounds.lowerBound <= bounds.upperBound && bounds.upperBound <= count) var result: [Element] = [] result.reserveCapacity(bounds.count) for index in indexMapping.matchingIndices[bounds] { result.append(parent[index]) } return ArraySlice(result) } override var value: Array { return indexMapping.matchingIndices.map { parent[$0] } } override var count: Int { return indexMapping.matchingIndices.count } } ================================================ FILE: Sources/ArrayFilteringOnPredicate.swift ================================================ // // ArrayFilteringOnPredicate.swift // GlueKit // // Created by Károly Lőrentey on 2016-09-26. // Copyright © 2015–2017 Károly Lőrentey. // extension ObservableArrayType { public func filter(_ isIncluded: @escaping (Element) -> Bool) -> AnyObservableArray { return ArrayFilteringOnPredicate(parent: self, isIncluded: isIncluded).anyObservableArray } } private final class ArrayFilteringOnPredicate: _BaseObservableArray { public typealias Element = Parent.Element public typealias Change = ArrayChange private struct ParentSink: UniqueOwnedSink { typealias Owner = ArrayFilteringOnPredicate unowned(unsafe) let owner: Owner func receive(_ update: ArrayUpdate) { owner.applyParentUpdate(update) } } private let parent: Parent private let isIncluded: (Element) -> Bool private var indexMapping: ArrayFilteringIndexmap init(parent: Parent, isIncluded: @escaping (Element) -> Bool) { self.parent = parent self.isIncluded = isIncluded self.indexMapping = ArrayFilteringIndexmap(initialValues: parent.value, isIncluded: isIncluded) super.init() parent.add(ParentSink(owner: self)) } deinit { parent.remove(ParentSink(owner: self)) } func applyParentUpdate(_ update: ArrayUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): let filteredChange = self.indexMapping.apply(change) if !filteredChange.isEmpty { sendChange(filteredChange) } case .endTransaction: endTransaction() } } override var isBuffered: Bool { return false } override subscript(index: Int) -> Element { return parent[indexMapping.matchingIndices[index]] } override subscript(bounds: Range) -> ArraySlice { precondition(0 <= bounds.lowerBound && bounds.lowerBound <= bounds.upperBound && bounds.upperBound <= count) var result: [Element] = [] result.reserveCapacity(bounds.count) for index in indexMapping.matchingIndices[bounds] { result.append(parent[index]) } return ArraySlice(result) } override var value: Array { return indexMapping.matchingIndices.map { parent[$0] } } override var count: Int { return indexMapping.matchingIndices.count } } ================================================ FILE: Sources/ArrayFolding.swift ================================================ // // ArrayFolding.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-09. // Copyright © 2015–2017 Károly Lőrentey. // extension ObservableArrayType { /// Returns an observable whose value is always equal to `self.value.reduce(initial, add)`. /// /// - Parameter initial: The accumulation starts with this initial value. /// - Parameter add: A closure that adds an element of the array into an accumulated value. /// - Parameter remove: A closure that cancels the effect of an earlier `add`. /// - Returns: An observable value for the reduction of this array. /// /// - Note: Elements are added and removed in no particular order. /// (I.e., the underlying binary operation over `Result` must form an abelian group.) /// /// - SeeAlso: `sum()` which returns a reduction using addition. public func reduce(_ initial: Result, add: @escaping (Result, Element) -> Result, remove: @escaping (Result, Element) -> Result) -> AnyObservableValue { return ArrayFoldingByTwoWayFunction(base: self, initial: initial, add: add, remove: remove).anyObservableValue } } extension ObservableArrayType where Element: BinaryInteger { /// Return the (observable) sum of the elements contained in this array. public func sum() -> AnyObservableValue { return reduce(0, add: +, remove: -) } } private class ArrayFoldingByTwoWayFunction: _BaseObservableValue { private var _value: Value let add: (Value, Base.Element) -> Value let remove: (Value, Base.Element) -> Value var connection: Connection? = nil init(base: Base, initial: Value, add: @escaping (Value, Base.Element) -> Value, remove: @escaping (Value, Base.Element) -> Value) { self._value = base.value.reduce(initial, add) self.add = add self.remove = remove super.init() connection = base.updates.subscribe { [unowned self] in self.apply($0) } } deinit { connection!.disconnect() } private func apply(_ update: ArrayUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): let old = _value change.forEachOld { _value = remove(_value, $0) } change.forEachNew { _value = add(_value, $0) } sendChange(ValueChange(from: old, to: _value)) case .endTransaction: endTransaction() } } override var value: Value { return _value } } ================================================ FILE: Sources/ArrayGatheringSource.swift ================================================ // // ArrayGatheringSource.swift // GlueKit // // Created by Károly Lőrentey on 2017-09-05. // Copyright © 2017 Károly Lőrentey. All rights reserved. // extension ObservableArrayType where Element: SourceType { public func gather() -> AnySource { return ArrayGatheringSource(self).anySource } } private class ArrayGatheringSource: _AbstractSource where Origin.Element: SourceType, Origin.Element.Value == Value { let origin: Origin var sinks: Set> = [] private struct GatherSink: UniqueOwnedSink { typealias Owner = ArrayGatheringSource unowned let owner: Owner func receive(_ value: ArrayUpdate) { guard case let .change(change) = value else { return } change.forEachOld { source in for sink in owner.sinks { source.remove(sink) } } change.forEachNew { source in for sink in owner.sinks { source.add(sink) } } } } init(_ origin: Origin) { self.origin = origin } override func add(_ sink: Sink) where Sink.Value == Value { if sinks.isEmpty { origin.add(GatherSink(owner: self)) } let new = sinks.insert(sink.anySink).inserted precondition(new) for source in origin.value { source.add(sink) } } @discardableResult override func remove(_ sink: Sink) -> Sink where Sink.Value == Value { let result = sinks.remove(sink.anySink)! for source in origin.value { source.remove(result) } if sinks.isEmpty { origin.remove(GatherSink(owner: self)) } return result.opened()! } } ================================================ FILE: Sources/ArrayMappingForArrayField.swift ================================================ // // ArrayMappingForArrayField.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-07. // Copyright © 2015–2017 Károly Lőrentey. // import BTree extension ObservableArrayType { public func flatMap(_ key: @escaping (Element) -> Field) -> AnyObservableArray { return ArrayMappingForArrayField(parent: self, key: key).anyObservableArray } } /// Maintains an index mapping for an operation mapping an array of arrays into a single flat array. /// /// Terminology: /// - preindex, precount refers to indices/count of the original array of arrays. /// - postindex, postcount refers to indices/count of the resulting flat array. private struct Indexmap { // TODO: A weight-augmented tree-backed list would work much better here. // The ith element in this sorted bag gives us the starting postindex of the source array corresponding to preindex i. // The bag always ends with an extra element with the overall count of elements. private var postindices: SortedBag = [0] var precount: Int { return postindices.count - 1 } var postcount: Int { return postindices.last! } func preindex(for postindex: Int) -> (preindex: Int, offset: Int) { let p = postindices.indexOfLastElement(notAfter: postindex)! let start = postindices[p] let preindex = postindices.offset(of: p) return (preindex, postindex - start) } func postindex(for preindex: Int) -> Int { return postindices[preindex] } mutating func appendArray(withCount count: Int) { postindices.insert(postcount + count) } mutating func replaceArrays(in prerange: Range, withCounts counts: [Int]) { let postrange: Range = postindex(for: prerange.lowerBound) ..< postindex(for: prerange.upperBound) let postdelta = counts.reduce(0, +) - postrange.count for i in CountableRange(prerange).reversed() { // TODO: SortedBag.remove(offsetsIn: prerange) postindices.remove(atOffset: i) } postindices.shift(startingAt: postindices.index(ofOffset: prerange.lowerBound), by: postdelta) var poststart = postrange.lowerBound for i in 0 ..< counts.count { let count = counts[i] postindices.insert(poststart) poststart += count } } mutating func setCount(forArrayAt preindex: Int, from old: Int, to new: Int) { if old == new { return } var i = postindices.index(ofOffset: preindex) postindices.formIndex(after: &i) postindices.shift(startingAt: i, by: new - old) } } private final class ArrayMappingForArrayField: _BaseObservableArray { typealias Element = Field.Element private final class FieldSink: SinkType, RefListElement { typealias Owner = ArrayMappingForArrayField unowned let owner: Owner let field: Field var refListLink = RefListLink() init(owner: Owner, field: Field) { self.owner = owner self.field = field field.add(self) } func disconnect() { field.remove(self) } func receive(_ update: ArrayUpdate) { owner.applyFieldUpdate(update, from: self) } } private struct ParentSink: UniqueOwnedSink { typealias Owner = ArrayMappingForArrayField unowned(unsafe) let owner: Owner func receive(_ update: ArrayUpdate) { owner.applyParentUpdate(update) } } private let parent: Parent private let key: (Parent.Element) -> Field private var fieldSinks = RefList() private var indexMapping = Indexmap() init(parent: Parent, key: @escaping (Parent.Element) -> Field) { self.parent = parent self.key = key super.init() parent.updates.add(ParentSink(owner: self)) for pe in parent.value { let field = key(pe) fieldSinks.append(FieldSink(owner: self, field: field)) indexMapping.appendArray(withCount: field.count) } } deinit { parent.updates.remove(ParentSink(owner: self)) fieldSinks.forEach { $0.disconnect() } } private func applyParentUpdate(_ update: ArrayUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): precondition(change.initialCount == fieldSinks.count) var transformedChange = ArrayChange(initialCount: indexMapping.postcount) for mod in change.modifications { let preindex = mod.startIndex let postindex = indexMapping.postindex(for: preindex) let oldFields = mod.oldElements.map { key($0) } let newFields = mod.newElements.map { key($0) } // Replace field connections. let prerange: Range = preindex ..< preindex + oldFields.count self.fieldSinks.forEach(in: prerange) { $0.disconnect() } self.fieldSinks.replaceSubrange(prerange, with: newFields.map { FieldSink(owner: self, field: $0) }) // Update index mapping. indexMapping.replaceArrays(in: prerange, withCounts: newFields.map { $0.count }) // Create new change component. let oldValues = oldFields.flatMap { $0.value } let newValues = newFields.flatMap { $0.value } if let mod = ArrayModification(replacing: oldValues, at: postindex, with: newValues) { transformedChange.add(mod) } } precondition(change.finalCount == fieldSinks.count) if !transformedChange.isEmpty { sendChange(transformedChange) } case .endTransaction: endTransaction() } } private func applyFieldUpdate(_ update: ArrayUpdate, from sink: FieldSink) { switch update { case .beginTransaction: beginTransaction() case .change(let change): let preindex = fieldSinks.index(of: sink)! let postindex = indexMapping.postindex(for: preindex) let transformedChange = change.widen(startIndex: postindex, initialCount: indexMapping.postcount) indexMapping.setCount(forArrayAt: preindex, from: change.initialCount, to: change.finalCount) sendChange(transformedChange) case .endTransaction: endTransaction() } } override var isBuffered: Bool { return false } override subscript(index: Int) -> Element { let (preindex, offset) = indexMapping.preindex(for: index) return key(parent[preindex])[offset] } override subscript(bounds: Range) -> ArraySlice { let start = indexMapping.preindex(for: bounds.lowerBound) let end = indexMapping.preindex(for: bounds.upperBound) if start.preindex == end.preindex { return key(parent[start.preindex])[start.offset ..< end.offset] } var result: [Element] = [] result.reserveCapacity(bounds.count) let firstField = key(parent[start.preindex]) result.append(contentsOf: firstField[start.offset ..< firstField.count]) for i in start.preindex + 1 ..< end.preindex { result.append(contentsOf: key(parent[i]).value) } let lastField = key(parent[end.preindex]) result.append(contentsOf: lastField[0 ..< end.offset]) precondition(result.count == bounds.count) return ArraySlice(result) } override var value: Array { return parent.value.flatMap { key($0).value } } override var count: Int { return indexMapping.postcount } } ================================================ FILE: Sources/ArrayMappingForValue.swift ================================================ // // ArrayMappingForValue.swift // GlueKit // // Created by Károly Lőrentey on 2016-08-22. // Copyright © 2015–2017 Károly Lőrentey. // extension ObservableArrayType { public func map(_ transform: @escaping (Element) -> Output) -> AnyObservableArray { return ArrayMappingForValue(input: self, transform: transform).anyObservableArray } } private final class ArrayMappingForValue: _AbstractObservableArray { typealias Change = ArrayChange let input: Input let transform: (Input.Element) -> Element let updateSource: AnySource> init(input: Input, transform: @escaping (Input.Element) -> Element) { self.input = input self.transform = transform self.updateSource = input.updates.map { u in u.map { c in c.map(transform) } } super.init() } override var isBuffered: Bool { return false } override subscript(index: Int) -> Element { return transform(input[index]) } override subscript(bounds: Range) -> ArraySlice { return ArraySlice(input[bounds].map(transform)) } override var count: Int { return input.count } override var value: [Element] { return input.value.map(transform) } override func add(_ sink: Sink) where Sink.Value == Update { updateSource.add(sink) } @discardableResult override func remove(_ sink: Sink) -> Sink where Sink.Value == Update { return updateSource.remove(sink) } override var observableCount: AnyObservableValue { return input.observableCount } } extension ObservableArrayType { public func bufferedMap(_ transform: @escaping (Element) -> Output) -> AnyObservableArray { return BufferedArrayMappingForValue(self, transform: transform).anyObservableArray } } private class BufferedArrayMappingForValue: _BaseObservableArray where Content.Element == Input { typealias Element = Output typealias Change = ArrayChange private struct BufferedMapSink: UniqueOwnedSink { typealias Owner = BufferedArrayMappingForValue unowned(unsafe) let owner: Owner func receive(_ update: ArrayUpdate) { owner.apply(update) } } let content: Content let transform: (Input) -> Output private var _value: [Output] private var pendingChange: ArrayChange? = nil init(_ content: Content, transform: @escaping (Input) -> Output) { self.content = content self.transform = transform self._value = content.value.map(transform) super.init() content.add(BufferedMapSink(owner: self)) } deinit { content.remove(BufferedMapSink(owner: self)) } func apply(_ update: Update>) { switch update { case .beginTransaction: beginTransaction() case .change(let change): if pendingChange != nil { pendingChange!.merge(with: change) } else { pendingChange = change } case .endTransaction: if let change = pendingChange { pendingChange = nil if isConnected { var mappedChange = Change(initialCount: value.count) for modification in change.modifications { switch modification { case .insert(let new, at: let index): let tnew = transform(new) mappedChange.add(.insert(tnew, at: index)) _value.insert(tnew, at: index) case .remove(_, at: let index): let old = _value.remove(at: index) mappedChange.add(.remove(old, at: index)) case .replace(_, at: let index, with: let new): let old = value[index] let tnew = transform(new) _value[index] = tnew mappedChange.add(.replace(old, at: index, with: tnew)) case .replaceSlice(let old, at: let index, with: let new): let range = index ..< index + old.count let told = Array(value[range]) let tnew = new.map(transform) mappedChange.add(.replaceSlice(told, at: index, with: tnew)) _value.replaceSubrange(range, with: tnew) } } sendChange(mappedChange) } else { for modification in change.modifications { switch modification { case .insert(let new, at: let index): _value.insert(transform(new), at: index) case .remove(_, at: let index): _value.remove(at: index) case .replace(_, at: let index, with: let new): _value[index] = transform(new) case .replaceSlice(let old, at: let index, with: let new): _value.replaceSubrange(index ..< index + old.count, with: new.map(transform)) } } } } endTransaction() } } override var isBuffered: Bool { return true } override subscript(_ index: Int) -> Element { return value[index] } override subscript(_ range: Range) -> ArraySlice { return value[range] } override var value: [Element] { return _value } override var count: Int { return value.count } } ================================================ FILE: Sources/ArrayMappingForValueField.swift ================================================ // // ArrayMappingForValueField.swift // GlueKit // // Created by Károly Lőrentey on 2015-12-10. // Copyright © 2015–2017 Károly Lőrentey. // extension ObservableArrayType { /// Return an observable array that consists of the values for the field specified by `key` for each element of this array. public func map(_ key: @escaping (Element) -> Field) -> AnyObservableArray { return ArrayMappingForValueField(parent: self, key: key).anyObservableArray } } private final class ArrayMappingForValueField: _BaseObservableArray { typealias Element = Field.Value typealias Change = ArrayChange private final class FieldSink: SinkType, RefListElement { unowned let owner: ArrayMappingForValueField let field: Field var refListLink = RefListLink() init(owner: ArrayMappingForValueField, field: Field) { self.owner = owner self.field = field field.add(self) } func disconnect() { field.remove(self) } func receive(_ update: ValueUpdate) { owner.applyFieldUpdate(update, from: self) } } private struct ParentSink: UniqueOwnedSink { typealias Owner = ArrayMappingForValueField unowned let owner: Owner func receive(_ update: ArrayUpdate) { owner.applyParentUpdate(update) } } private let parent: Parent private let key: (Parent.Element) -> Field private var fieldSinks = RefList() init(parent: Parent, key: @escaping (Parent.Element) -> Field) { self.parent = parent self.key = key } override var isBuffered: Bool { return false } override subscript(index: Int) -> Element { return key(parent[index]).value } override subscript(bounds: Range) -> ArraySlice { return ArraySlice(parent[bounds].map { key($0).value }) } override var value: [Element] { return parent.value.map { key($0).value } } override var count: Int { return parent.count } override func activate() { let fields = parent.value.map(key) parent.add(ParentSink(owner: self)) fieldSinks = RefList(fields.lazy.map { field in FieldSink(owner: self, field: field) }) } override func deactivate() { parent.remove(ParentSink(owner: self)) fieldSinks.forEach { $0.disconnect() } fieldSinks.removeAll() } private func applyFieldUpdate(_ update: ValueUpdate, from sink: FieldSink) { switch update { case .beginTransaction: beginTransaction() case .change(let change): let index = fieldSinks.index(of: sink)! sendChange(ArrayChange(initialCount: fieldSinks.count, modification: .replace(change.old, at: index, with: change.new))) case .endTransaction: endTransaction() } } func applyParentUpdate(_ update: ArrayUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): precondition(fieldSinks.count == change.initialCount) var newChange = ArrayChange(initialCount: change.initialCount) for mod in change.modifications { let start = mod.startIndex var i = start mod.forEachOldElement { old in fieldSinks[i].disconnect() i += 1 } var sinks: [FieldSink] = [] mod.forEachNewElement { new in let field = key(new) sinks.append(FieldSink(owner: self, field: field)) } fieldSinks.replaceSubrange(start ..< i, with: sinks) newChange.add(mod.map { self.key($0).value }) } precondition(fieldSinks.count == change.finalCount) if !newChange.isEmpty { sendChange(newChange) } case .endTransaction: endTransaction() } } } ================================================ FILE: Sources/ArrayReference.swift ================================================ // // ArrayReference.swift // GlueKit // // Created by Károly Lőrentey on 2016-08-17. // Copyright © 2015–2017 Károly Lőrentey. // extension ObservableValueType where Value: ObservableArrayType{ public func unpacked() -> AnyObservableArray { return UnpackedObservableArrayReference(self).anyObservableArray } } /// A mutable reference to an `AnyObservableArray` that's also an observable array. /// You can switch to another target array without having to re-register subscribers. private final class UnpackedObservableArrayReference: _BaseObservableArray where Reference.Value: ObservableArrayType { typealias Target = Reference.Value typealias Element = Target.Element typealias Change = ArrayChange private struct ReferenceSink: UniqueOwnedSink { typealias Owner = UnpackedObservableArrayReference unowned(unsafe) let owner: Owner func receive(_ update: ValueUpdate) { owner.applyReferenceUpdate(update) } } private struct TargetSink: UniqueOwnedSink { typealias Owner = UnpackedObservableArrayReference unowned(unsafe) let owner: Owner func receive(_ update: ArrayUpdate) { owner.applyTargetUpdate(update) } } private var _reference: Reference private var _target: Reference.Value? = nil // Retained to make sure we keep it alive init(_ reference: Reference) { _reference = reference super.init() } override func activate() { _reference.updates.add(ReferenceSink(owner: self)) let target = _reference.value _target = target target.updates.add(TargetSink(owner: self)) } override func deactivate() { _target!.updates.remove(TargetSink(owner: self)) _reference.updates.remove(ReferenceSink(owner: self)) } func applyReferenceUpdate(_ update: ValueUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): if isConnected { _target!.remove(TargetSink(owner: self)) _target = change.new _target!.add(TargetSink(owner: self)) sendChange(ArrayChange(from: change.old.value, to: change.new.value)) } case .endTransaction: endTransaction() } } func applyTargetUpdate(_ update: ArrayUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): sendChange(change) case .endTransaction: endTransaction() } } override var isBuffered: Bool { return false } override subscript(_ index: Int) -> Element { return _reference.value[index] } override subscript(_ range: Range) -> ArraySlice { return _reference.value[range] } override var value: [Element] { return _reference.value.value } override var count: Int { return _reference.value.count } } ================================================ FILE: Sources/ArrayVariable.swift ================================================ // // ArrayVariable.swift // GlueKit // // Created by Károly Lőrentey on 2015-12-08. // Copyright © 2015–2017 Károly Lőrentey. // open class ArrayVariable: _BaseUpdatableArray, ExpressibleByArrayLiteral { public typealias Value = Array public typealias Change = ArrayChange fileprivate var _value: [Element] public override init() { _value = [] } public init(_ elements: [Element]) { _value = elements } public init(_ elements: S) where S.Iterator.Element == Element { _value = Array(elements) } public init(elements: Element...) { _value = elements } public required convenience init(arrayLiteral elements: Element...) { self.init(elements) } override func rawApply(_ change: ArrayChange) { _value.apply(change) } final public override var value: [Element] { get { return _value } set { if isConnected { let old = _value beginTransaction() _value = newValue sendChange(ArrayChange(from: old, to: newValue)) endTransaction() } else { _value = newValue } } } final public override var count: Int { return _value.count } final public override var isBuffered: Bool { return true } final public override subscript(index: Int) -> Element { get { return _value[index] } set { if isConnected { let old = _value[index] beginTransaction() _value[index] = newValue sendChange(ArrayChange(initialCount: _value.count, modification: .replace(old, at: index, with: newValue))) endTransaction() } else { _value[index] = newValue } } } final public override subscript(bounds: Range) -> ArraySlice { get { return value[bounds] } set { if isConnected { let oldCount = _value.count let old = Array(_value[bounds]) beginTransaction() _value[bounds] = newValue sendChange(ArrayChange(initialCount: oldCount, modification: .replaceSlice(old, at: bounds.lowerBound, with: Array(newValue)))) endTransaction() } else { _value[bounds] = newValue } } } } ================================================ FILE: Sources/BracketingSource.swift ================================================ // // BracketingSource.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-25. // Copyright © 2015–2017 Károly Lőrentey. // extension SourceType { /// Returns a version of this source that optionally prefixes or suffixes all observers' received values /// with computed start/end values. /// /// For each new subscriber, the returned source evaluates `hello`; if it returns a non-nil value, /// the value is sent to the sink; then the sink is added to `self`. /// /// For each subscriber that is to be removed, the returned source first removes it from `self`, then /// evaluates `goodbye`; if it returns a non-nil value, the bracketing source sends it to the sink. public func bracketed(hello: @escaping () -> Value?, goodbye: @escaping () -> Value?) -> AnySource { return BracketingSource(self, hello: hello, goodbye: goodbye).anySource } } private class BracketingSink: SinkType { typealias Value = Sink.Value let sink: Sink var pendingValues: [Value]? var removed = false init(_ sink: Sink) { self.sink = sink self.pendingValues = nil } init(_ sink: Sink, _ initial: Value) { self.sink = sink self.pendingValues = [initial] } func receive(_ value: Value) { if pendingValues == nil { sink.receive(value) } else { pendingValues!.append(value) } } var hashValue: Int { return sink.hashValue } static func ==(left: BracketingSink, right: BracketingSink) -> Bool { return left.sink == right.sink } } private final class BracketingSource: _AbstractSource { typealias Value = Input.Value let input: Input let hello: () -> Value? let goodbye: () -> Value? init(_ input: Input, hello: @escaping () -> Value?, goodbye: @escaping () -> Value?) { self.input = input self.hello = hello self.goodbye = goodbye } final override func add(_ sink: Sink) where Sink.Value == Value { if let greeting = hello() { let bracketing = BracketingSink(sink, greeting) input.add(bracketing) while !bracketing.pendingValues!.isEmpty && !bracketing.removed { let value = bracketing.pendingValues!.removeFirst() bracketing.sink.receive(value) } bracketing.pendingValues = nil } else { input.add(BracketingSink(sink)) } } @discardableResult final override func remove(_ sink: Sink) -> Sink where Sink.Value == Value { let old = input.remove(BracketingSink(sink)) old.removed = true if let farewell = goodbye() { old.sink.receive(farewell) } return old.sink } } ================================================ FILE: Sources/BufferedArray.swift ================================================ // // BufferedArray.swift // GlueKit // // Created by Károly Lőrentey on 2016-08-22. // Copyright © 2015–2017 Károly Lőrentey. // extension ObservableArrayType { public func buffered() -> AnyObservableArray { if isBuffered { return anyObservableArray } return BufferedObservableArray(self).anyObservableArray } } internal class BufferedObservableArray: _BaseObservableArray { typealias Element = Content.Element typealias Change = ArrayChange private struct BufferedSink: UniqueOwnedSink { typealias Owner = BufferedObservableArray unowned(unsafe) let owner: Owner func receive(_ update: ArrayUpdate) { owner.applyUpdate(update) } } private let _content: Content private var _value: [Element] private var _pendingChange: Change? = nil init(_ content: Content) { _content = content _value = content.value super.init() _content.add(BufferedSink(owner: self)) } deinit { _content.remove(BufferedSink(owner: self)) } func applyUpdate(_ update: ArrayUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): if _pendingChange != nil { _pendingChange!.merge(with: change) } else { _pendingChange = change } case .endTransaction: if let change = _pendingChange { _value.apply(change) _pendingChange = nil sendChange(change) } endTransaction() } } override var isBuffered: Bool { return true } override subscript(_ index: Int) -> Content.Element { return _value[index] } override subscript(_ range: Range) -> ArraySlice { return _value[range] } override var value: [Element] { return _value } override var count: Int { return _value.count } } ================================================ FILE: Sources/BufferedSet.swift ================================================ // // BufferedSet.swift // GlueKit // // Created by Károly Lőrentey on 2016-11-02. // Copyright © 2015–2017 Károly Lőrentey. // extension ObservableSetType { public func buffered() -> AnyObservableSet { if isBuffered { return anyObservableSet } return BufferedObservableSet(self).anyObservableSet } } internal class BufferedObservableSet: _BaseObservableSet { typealias Element = Content.Element typealias Change = SetChange private struct BufferedSink: UniqueOwnedSink { typealias Owner = BufferedObservableSet unowned(unsafe) let owner: Owner func receive(_ update: SetUpdate) { owner.applyUpdate(update) } } private let _content: Content private var _value: Set private var _pendingChange: Change? = nil init(_ content: Content) { _content = content _value = content.value super.init() _content.add(BufferedSink(owner: self)) } deinit { _content.remove(BufferedSink(owner: self)) } func applyUpdate(_ update: SetUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): if _pendingChange != nil { _pendingChange!.merge(with: change) } else { _pendingChange = change } case .endTransaction: if let change = _pendingChange { _value.apply(change) _pendingChange = nil sendChange(change) } endTransaction() } } override var isBuffered: Bool { return true } override var count: Int { return _value.count } override var value: Set { return _value } override func contains(_ member: Element) -> Bool { return _value.contains(member) } override func isSubset(of other: Set) -> Bool { return _value.isSubset(of: other) } override func isSuperset(of other: Set) -> Bool { return _value.isSuperset(of: other) } } ================================================ FILE: Sources/BufferedSource.swift ================================================ // // BufferedSource.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-25. // Copyright © 2015–2017 Károly Lőrentey. // extension SourceType { public func buffered() -> AnySource { return BufferedSource(self).anySource } } private final class BufferedSource: SignalerSource, SinkType { typealias Value = Input.Value private let source: Input init(_ source: Input) { self.source = source super.init() } override func activate() { source.add(self.unowned()) } override func deactivate() { source.remove(self.unowned()) } func receive(_ value: Input.Value) { signal.send(value) } } ================================================ FILE: Sources/BufferedValue.swift ================================================ // // Observable Transformations.swift // GlueKit // // Created by Károly Lőrentey on 2015-12-07. // Copyright © 2015–2017 Károly Lőrentey. // extension ObservableValueType { public func buffered() -> AnyObservableValue { return BufferedObservableValue(self).anyObservableValue } } private struct BufferedObservableSink: UniqueOwnedSink { typealias Owner = BufferedObservableValue unowned(unsafe) let owner: Owner func receive(_ update: ValueUpdate) { owner.apply(update) } } private class BufferedObservableValue: _BaseObservableValue { typealias Value = Base.Value private var _base: Base private var _value: Value private var _pending: Value? = nil init(_ base: Base) { self._base = base self._value = base.value super.init() _base.updates.add(BufferedObservableSink(owner: self)) } deinit { _base.updates.remove(BufferedObservableSink(owner: self)) } func apply(_ update: ValueUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): _pending = change.new case .endTransaction: if let pending = _pending { let old = _value _value = pending _pending = nil sendChange(.init(from: old, to: _value)) } endTransaction() } } override var value: Value { return _value } } ================================================ FILE: Sources/CADisplayLink Extensions.swift ================================================ // // CADisplayLink Extensions.swift // GlueKit // // Created by Károly Lőrentey on 2016-03-17. // Copyright © 2015–2017 Károly Lőrentey. // #if os(iOS) import QuartzCore private var associatedTargetKey: UInt8 = 0 public class CADisplayLinkSource: SignalerSource { public typealias Value = CADisplayLink private var runLoop: RunLoop? = nil public var displayLink: CADisplayLink? = nil public override init() { super.init() } override func activate() { displayLink = CADisplayLink(target: self, selector: #selector(CADisplayLinkSource.tick(_:))) precondition(self.runLoop == nil) let runLoop = RunLoop.current self.runLoop = runLoop displayLink!.add(to: runLoop, forMode: RunLoopMode.commonModes) } override func deactivate() { precondition(runLoop != nil) displayLink!.remove(from: runLoop!, forMode: RunLoopMode.commonModes) displayLink = nil runLoop = nil } @objc private func tick(_ displayLink: CADisplayLink) { signal.send(displayLink) } } #endif ================================================ FILE: Sources/Change.swift ================================================ // // Change.swift // GlueKit // // Created by Károly Lőrentey on 2016-08-12. // Copyright © 2015–2017 Károly Lőrentey. // /// Describes a change to an observable value. /// An instance of a type implementing this protocol contains just enough information to describe the difference /// between the old value and the new value of the observable. public protocol ChangeType { associatedtype Value /// Creates a new change description for a change that goes from `oldValue` to `newValue`. init(from oldValue: Value, to newValue: Value) /// Returns true if this change did not actually change the value of the observable. /// Noop changes aren't usually sent by observables, but it is possible to get them by merging a sequence of /// changes to a collection. var isEmpty: Bool { get } /// Applies this change on `value` in place. /// Note that not all changes may be applicable on all values. func apply(on value: inout Value) /// Applies this change on `value` and returns the result. /// Note that not all changes may be applicable on all values. func applied(on value: Value) -> Value /// Merge this change with the `next` change. The result is a single change description that describes the /// change of performing `self` followed by `next`. /// /// The resulting instance may take a shortcut when producing the result value if some information in `self` /// is overwritten by `next`. func merged(with next: Self) -> Self mutating func merge(with next: Self) /// Reverse the direction of this change, i.e., return a change that undoes the effect of this change. func reversed() -> Self } extension ChangeType { /// Applies this change on `value` and returns the result. /// Note that not all changes may be applicable on all values. public func applied(on value: Value) -> Value { var result = value self.apply(on: &result) return result } public func merged(with next: Self) -> Self { var temp = self temp.merge(with: next) return temp } } ================================================ FILE: Sources/ChangesSource.swift ================================================ // // ChangesSource.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-22. // Copyright © 2015–2017 Károly Lőrentey. // extension ObservableType { /// A source that reports changes to the value of this observable. /// Changes reported correspond to complete transactions in `self.updates`. public var changes: AnySource { return ChangesSource(self).anySource } } private class ChangesSinkState { typealias Value = Update var pending: Change? = nil func apply(_ update: Update) -> Change? { switch update { case .beginTransaction: precondition(pending == nil) case .change(let change): if pending == nil { pending = change } else { pending!.merge(with: change) } case .endTransaction: if let change = pending { pending = nil if !change.isEmpty { return change } } } return nil } } private struct ChangesSink: SinkType where Wrapped.Value: ChangeType { typealias Change = Wrapped.Value typealias Value = Update let wrapped: Wrapped let state: ChangesSinkState? init(_ wrapped: Wrapped, withState needState: Bool) { self.wrapped = wrapped self.state = needState ? ChangesSinkState() : nil } func receive(_ update: Update) { if let change = state?.apply(update) { wrapped.receive(change) } } var hashValue: Int { return wrapped.hashValue } static func ==(left: ChangesSink, right: ChangesSink) -> Bool { return left.wrapped == right.wrapped } } internal class ChangesSource: _AbstractSource { typealias Change = Observable.Change let observable: Observable init(_ observable: Observable) { self.observable = observable } override func add(_ sink: Sink) where Sink.Value == Change { observable.add(ChangesSink(sink, withState: true)) } @discardableResult override func remove(_ sink: Sink) -> Sink where Sink.Value == Change { let old = observable.remove(ChangesSink(sink, withState: false)) return old.wrapped } } extension Connector { @discardableResult public func subscribe(_ observable: Observable, to sink: @escaping (Observable.Change) -> Void) -> Connection { return observable.changes.subscribe(sink).putInto(self) } } ================================================ FILE: Sources/CompositeObservable.swift ================================================ // // CompositeObservable.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-09. // Copyright © 2015–2017 Károly Lőrentey. // extension ObservableValueType { public func combined(_ other: Other) -> AnyObservableValue<(Value, Other.Value)> { return CompositeObservable(left: self, right: other, combinator: { ($0, $1) }).anyObservableValue } public func combined(_ a: A, _ b: B) -> AnyObservableValue<(Value, A.Value, B.Value)> { return combined(a).combined(b, by: { a, b in (a.0, a.1, b) }) } public func combined(_ a: A, _ b: B, _ c: C) -> AnyObservableValue<(Value, A.Value, B.Value, C.Value)> { return combined(a, b).combined(c, by: { a, b in (a.0, a.1, a.2, b) }) } public func combined(_ a: A, _ b: B, _ c: C, _ d: D) -> AnyObservableValue<(Value, A.Value, B.Value, C.Value, D.Value)> { return combined(a, b, c).combined(d, by: { a, b in (a.0, a.1, a.2, a.3, b) }) } public func combined(_ a: A, _ b: B, _ c: C, _ d: D, _ e: E) -> AnyObservableValue<(Value, A.Value, B.Value, C.Value, D.Value, E.Value)> { return combined(a, b, c, d).combined(e, by: { a, b in (a.0, a.1, a.2, a.3, a.4, b) }) } public func combined(_ other: Other, by combinator: @escaping (Value, Other.Value) -> Output) -> AnyObservableValue { return CompositeObservable(left: self, right: other, combinator: combinator).anyObservableValue } public func combined(_ a: A, _ b: B, by combinator: @escaping (Value, A.Value, B.Value) -> Output) -> AnyObservableValue { return combined(a).combined(b, by: { a, b in combinator(a.0, a.1, b) }) } public func combined(_ a: A, _ b: B, _ c: C, by combinator: @escaping (Value, A.Value, B.Value, C.Value) -> Output) -> AnyObservableValue { return combined(a, b).combined(c, by: { a, b in combinator(a.0, a.1, a.2, b) }) } public func combined(_ a: A, _ b: B, _ c: C, _ d: D, by combinator: @escaping (Value, A.Value, B.Value, C.Value, D.Value) -> Output) -> AnyObservableValue { return combined(a, b, c).combined(d, by: { a, b in combinator(a.0, a.1, a.2, a.3, b) }) } public func combined(_ a: A, _ b: B, _ c: C, _ d: D, _ e: E, by combinator: @escaping (Value, A.Value, B.Value, C.Value, D.Value, E.Value) -> Output) -> AnyObservableValue { return combined(a, b, c, d).combined(e, by: { a, b in combinator(a.0, a.1, a.2, a.3, a.4, b) }) } } private struct LeftSink: UniqueOwnedSink { typealias Owner = CompositeObservable unowned let owner: Owner func receive(_ update: ValueUpdate) { owner.applyLeftUpdate(update) } } private struct RightSink: UniqueOwnedSink { typealias Owner = CompositeObservable unowned let owner: Owner func receive(_ update: ValueUpdate) { owner.applyRightUpdate(update) } } /// An AnyObservableValue that is calculated from two other observables. private final class CompositeObservable: _BaseObservableValue { typealias Change = ValueChange private let left: Left private let right: Right private let combinator: (Left.Value, Right.Value) -> Value private var _leftValue: Left.Value? = nil private var _rightValue: Right.Value? = nil private var _value: Value? = nil public init(left: Left, right: Right, combinator: @escaping (Left.Value, Right.Value) -> Value) { self.left = left self.right = right self.combinator = combinator } deinit { assert(_value == nil) } public override var value: Value { if let value = _value { return value } return combinator(left.value, right.value) } internal override func activate() { assert(_value == nil) let v1 = left.value let v2 = right.value _leftValue = v1 _rightValue = v2 _value = combinator(v1, v2) left.add(LeftSink(owner: self)) right.add(RightSink(owner: self)) } internal override func deactivate() { left.remove(LeftSink(owner: self)) right.remove(RightSink(owner: self)) _value = nil _leftValue = nil _rightValue = nil } func applyLeftUpdate(_ update: ValueUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): _leftValue = change.new let old = _value! let new = combinator(_leftValue!, _rightValue!) _value = new sendChange(ValueChange(from: old, to: new)) case .endTransaction: endTransaction() } } func applyRightUpdate(_ update: ValueUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): _rightValue = change.new let old = _value! let new = combinator(_leftValue!, _rightValue!) _value = new sendChange(ValueChange(from: old, to: new)) case .endTransaction: endTransaction() } } } //MARK: Operations with observables of equatable values public func == (a: A, b: B) -> AnyObservableValue where A.Value == B.Value, A.Value: Equatable { return a.combined(b, by: ==) } public func != (a: A, b: B) -> AnyObservableValue where A.Value == B.Value, A.Value: Equatable { return a.combined(b, by: !=) } //MARK: Operations with observables of comparable values public func < (a: A, b: B) -> AnyObservableValue where A.Value == B.Value, A.Value: Comparable { return a.combined(b, by: <) } public func > (a: A, b: B) -> AnyObservableValue where A.Value == B.Value, A.Value: Comparable { return a.combined(b, by: >) } public func <= (a: A, b: B) -> AnyObservableValue where A.Value == B.Value, A.Value: Comparable { return a.combined(b, by: <=) } public func >= (a: A, b: B) -> AnyObservableValue where A.Value == B.Value, A.Value: Comparable { return a.combined(b, by: >=) } public func min(_ a: A, _ b: B) -> AnyObservableValue where A.Value == Value, B.Value == Value { return a.combined(b, by: min) } public func max(_ a: A, _ b: B) -> AnyObservableValue where A.Value == Value, B.Value == Value { return a.combined(b, by: max) } //MARK: Operations with observables of boolean values public prefix func ! (v: O) -> AnyObservableValue where O.Value == Bool { return v.map { !$0 } } public func && (a: A, b: B) -> AnyObservableValue where A.Value == Bool, B.Value == Bool { return a.combined(b, by: { a, b in a && b }) } public func || (a: A, b: B) -> AnyObservableValue where A.Value == Bool, B.Value == Bool { return a.combined(b, by: { a, b in a || b }) } //MARK: Operations with observables of integer arithmetic values public prefix func - (v: O) -> AnyObservableValue where O.Value: SignedNumeric { return v.map { -$0 } } public func + (a: A, b: B) -> AnyObservableValue where A.Value == Value, B.Value == Value { return a.combined(b, by: +) } public func - (a: A, b: B) -> AnyObservableValue where A.Value == Value, B.Value == Value { return a.combined(b, by: -) } public func * (a: A, b: B) -> AnyObservableValue where A.Value == Value, B.Value == Value { return a.combined(b, by: *) } public func / (a: A, b: B) -> AnyObservableValue where A.Value == Value, B.Value == Value { return a.combined(b, by: /) } public func % (a: A, b: B) -> AnyObservableValue where A.Value == Value, B.Value == Value { return a.combined(b, by: %) } //MARK: Operations with floating point values extension ObservableValueType where Value: FloatingPoint { public func squareRoot() -> AnyObservableValue { return self.map { $0.squareRoot() } } } public func + (a: A, b: B) -> AnyObservableValue where A.Value == Value, B.Value == Value { return a.combined(b, by: +) } public func - (a: A, b: B) -> AnyObservableValue where A.Value == Value, B.Value == Value { return a.combined(b, by: -) } public func * (a: A, b: B) -> AnyObservableValue where A.Value == Value, B.Value == Value { return a.combined(b, by: *) } public func / (a: A, b: B) -> AnyObservableValue where A.Value == Value, B.Value == Value { return a.combined(b, by: /) } ================================================ FILE: Sources/CompositeUpdatable.swift ================================================ // // CompositeUpdatable.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-09. // Copyright © 2015–2017 Károly Lőrentey. // extension UpdatableValueType { public func combined(_ other: Other) -> AnyUpdatableValue<(Value, Other.Value)> { return CompositeUpdatable(left: self, right: other).anyUpdatableValue } public func combined(_ a: A, _ b: B) -> AnyUpdatableValue<(Value, A.Value, B.Value)> { return combined(a).combined(b) .map({ a, b in (a.0, a.1, b) }, inverse: { v in ((v.0, v.1), v.2) }) } public func combined(_ a: A, _ b: B, _ c: C) -> AnyUpdatableValue<(Value, A.Value, B.Value, C.Value)> { return combined(a).combined(b).combined(c) .map({ a, b in (a.0.0, a.0.1, a.1, b) }, inverse: { v in (((v.0, v.1), v.2), v.3) }) } public func combined(_ a: A, _ b: B, _ c: C, _ d: D) -> AnyUpdatableValue<(Value, A.Value, B.Value, C.Value, D.Value)> { return combined(a).combined(b).combined(c).combined(d) .map({ a, b in (a.0.0.0, a.0.0.1, a.0.1, a.1, b) }, inverse: { v in ((((v.0, v.1), v.2), v.3), v.4) }) } public func combined(_ a: A, _ b: B, _ c: C, _ d: D, _ e: E) -> AnyUpdatableValue<(Value, A.Value, B.Value, C.Value, D.Value, E.Value)> { return combined(a).combined(b).combined(c).combined(d).combined(e) .map({ a, b in (a.0.0.0.0, a.0.0.0.1, a.0.0.1, a.0.1, a.1, b) }, inverse: { v in (((((v.0, v.1), v.2), v.3), v.4), v.5) }) } } private struct LeftSink: UniqueOwnedSink { typealias Owner = CompositeUpdatable unowned let owner: Owner func receive(_ update: ValueUpdate) { owner.applyLeftUpdate(update) } } private struct RightSink: UniqueOwnedSink { typealias Owner = CompositeUpdatable unowned let owner: Owner func receive(_ update: ValueUpdate) { owner.applyRightUpdate(update) } } /// An AnyUpdatableValue that is a composite of two other updatables. private final class CompositeUpdatable: _BaseUpdatableValue<(Left.Value, Right.Value)> { typealias Value = (Left.Value, Right.Value) typealias Change = ValueChange private let left: Left private let right: Right private var latest: Value? = nil init(left: Left, right: Right) { self.left = left self.right = right } override func rawGetValue() -> Value { if let latest = self.latest { return latest } return (left.value, right.value) } override func rawSetValue(_ value: Value) { left.apply(.beginTransaction) right.apply(.beginTransaction) left.value = value.0 right.value = value.1 right.apply(.endTransaction) left.apply(.endTransaction) } override func activate() { precondition(latest == nil) latest = (left.value, right.value) left.add(LeftSink(owner: self)) right.add(RightSink(owner: self)) } override func deactivate() { precondition(latest != nil) left.remove(LeftSink(owner: self)) right.remove(RightSink(owner: self)) latest = nil } func applyLeftUpdate(_ update: ValueUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): let old = latest! latest!.0 = change.new sendChange(Change(from: old, to: latest!)) case .endTransaction: endTransaction() } } func applyRightUpdate(_ update: ValueUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): let old = latest! latest!.1 = change.new sendChange(Change(from: old, to: latest!)) case .endTransaction: endTransaction() } } } ================================================ FILE: Sources/ComputedUpdatable.swift ================================================ // // ComputedUpdatable.swift // GlueKit // // Created by Károly Lőrentey on 2016-11-11. // Copyright © 2015–2017 Károly Lőrentey. // public final class ComputedUpdatable: _BaseUpdatableValue { public let getter: () -> Value public let setter: (Value) -> () public let refreshSource: AnySource? private var _value: Value private struct Sink: UniqueOwnedSink { typealias Owner = ComputedUpdatable unowned(unsafe) let owner: Owner func receive(_ value: Void) { owner.refresh() } } public init(getter: @escaping () -> Value, setter: @escaping (Value) -> (), refreshSource: AnySource? = nil) { self.getter = getter self.setter = setter self.refreshSource = refreshSource self._value = getter() super.init() refreshSource?.add(Sink(owner: self)) } deinit { refreshSource?.remove(Sink(owner: self)) } override func rawGetValue() -> Value { return _value } override func rawSetValue(_ value: Value) { setter(value) _value = getter() } public func refresh() { self.value = getter() } } ================================================ FILE: Sources/Connect.swift ================================================ // // Connect.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-22. // Copyright © 2015–2017 Károly Lőrentey. // extension SourceType { /// Connect `sink` to this source. The sink will receive all values that this source produces in the future. /// The connection will be kept active until the returned connection object is deallocated or explicitly disconnected. /// /// In GlueKit, a connection holds strong references to both its source and sink; thus sources (and sinks) are kept /// alive at least as long as they have an active connection. public func subscribe(_ sink: @escaping (Value) -> Void) -> Connection { return ConcreteConnection(source: self, sink: sink) } } /// An object that controls the lifetime of a closure's subscription to a source. /// /// The closure's subscription to the source remains active until this object is deallocated /// or `disconnect` is called on it. public class Connection { internal init() {} public func disconnect() { abstract() } } /// An object that controls the lifetime of a closure's subscription to a particular source. internal class ConcreteConnection: Connection { typealias Value = Source.Value var source: Source? var sink: Optional<(Value) -> Void> init(source: Source, sink: @escaping (Value) -> Void) { self.source = source self.sink = sink super.init() // Wrap the closure in a sink and add it to the source. source.add(ClosureSink(ObjectIdentifier(self), sink)) } deinit { disconnect() } public override func disconnect() { guard let source = self.source, let sink = self.sink else { return } // Construct a dummy `ClosureSink` that looks identical to the original one and remove it from the source. // At first glance, we could use a dummy closure here, because the closure isn't involved in the sink's identity. // However, sources sometimes synchronously send farewell values to removed sinks using the instance // given here --- so using e.g. an empty closure would lose these. source.remove(ClosureSink(ObjectIdentifier(self), sink)) // Release resources associated with this connection. self.source = nil self.sink = nil } } private struct ClosureSinkData { /// The connection of this sink, serving as the unique identifier of it. unowned let connection: Connection } /// A Sink that wraps a closure. `Hashable` is implemented by using the identity of the unique `Connection` /// object associated with the subscription. internal struct ClosureSink: SinkType { /// The object identifier of connection object. /// The natural choice would be to use an unowned reference to the connection object itself; /// but `Connection`'s `deinit` needs to be able to create `ClosureSink`s, and it's not a good /// idea to create unowned references during deinit -- the semantics are unclear to me, and I get crashes. private let identifier: ObjectIdentifier /// The closure that is to be called when this sink receives a value. private let sink: (Value) -> Void init(_ identifier: ObjectIdentifier, _ sink: @escaping (Value) -> Void) { self.identifier = identifier self.sink = sink } func receive(_ value: Value) { sink(value) } var hashValue: Int { return identifier.hashValue } static func ==(left: ClosureSink, right: ClosureSink) -> Bool { // Sink equality is based on the identity of the connection. return left.identifier == right.identifier } } extension Connector { @discardableResult public func connect(_ source: Source, to sink: @escaping (Source.Value) -> Void) -> Connection { return source.subscribe(sink).putInto(self) } } ================================================ FILE: Sources/Connector.swift ================================================ // // Connector.swift // GlueKit // // Created by Károly Lőrentey on 2015-11-30. // Copyright © 2015–2017 Károly Lőrentey. // extension Connection { /// Put this connection into `connector`. The connector will disconnect the connection when it is deallocated. @discardableResult public func putInto(_ connector: Connector) -> Connection { connector.add(self) return self } } /// A class for controlling the lifecycle of connections. /// The connector owns a set of connections and forces them to disconnect when it is deallocated. public class Connector { private var connections: [Connection] = [] public init() {} deinit { disconnect() } fileprivate func add(_ connection: Connection) { connections.append(connection) } public func disconnect() { let cs = connections connections.removeAll() for c in cs { c.disconnect() } } } ================================================ FILE: Sources/DependentValue.swift ================================================ // // DependentValue.swift // GlueKit // // Created by Károly Lőrentey on 2017-04-23. // Copyright © 2015–2017 Károly Lőrentey. // infix operator <-- public func <-- (target: DependentValue, source: Source) where Source.Value == Value { target.origin = source.anyObservableValue } public class DependentValue { private let setter: (Value) -> () private var transactions: Int = 0 private var pending: Value? internal var origin: AnyObservableValue? { didSet { receive(.beginTransaction) oldValue?.remove(Sink(owner: self)) if let origin = origin { pending = origin.value origin.add(Sink(owner: self)) } receive(.endTransaction) } } private struct Sink: UniqueOwnedSink { typealias Owner = DependentValue unowned(unsafe) let owner: Owner func receive(_ update: ValueUpdate) { owner.receive(update) } } public init(setter: @escaping (Value) -> ()) { self.setter = setter self.origin = nil } public init(origin: Origin, setter: @escaping (Value) -> ()) where Origin.Value == Value { self.setter = setter self.origin = origin.anyObservableValue origin.add(Sink(owner: self)) } deinit { origin?.remove(Sink(owner: self)) } func receive(_ update: ValueUpdate) { switch update { case .beginTransaction: transactions += 1 case .change(let change): pending = change.new case .endTransaction: transactions -= 1 if transactions == 0, let pending = pending { self.pending = nil setter(pending) } } } } ================================================ FILE: Sources/DispatchSource.swift ================================================ // // DispatchSource.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-24. // Copyright © 2015–2017 Károly Lőrentey. // import Foundation extension SourceType { public func dispatch(on queue: DispatchQueue) -> AnySource { return TransformedSource(input: self, transform: SinkTransformForDispatchQueue(queue)).anySource } public func dispatch(on queue: OperationQueue) -> AnySource { return TransformedSource(input: self, transform: SinkTransformForOperationQueue(queue)).anySource } } final class SinkTransformForDispatchQueue: SinkTransform { typealias Input = Value typealias Output = Value let queue: DispatchQueue init(_ queue: DispatchQueue) { self.queue = queue } func apply(_ input: Value, _ sink: Sink) where Sink.Value == Value { queue.async { sink.receive(input) } } } final class SinkTransformForOperationQueue: SinkTransform { typealias Input = Value typealias Output = Value let queue: OperationQueue init(_ queue: OperationQueue) { self.queue = queue } func apply(_ input: Value, _ sink: Sink) where Sink.Value == Value { if OperationQueue.current == queue { sink.receive(input) } else { queue.addOperation { sink.receive(input) } } } } ================================================ FILE: Sources/DistinctUnion.swift ================================================ // // DistinctUnion.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-04. // Copyright © 2015–2017 Károly Lőrentey. // extension ObservableArrayType where Element: Hashable { /// Returns an observable set that contains the same elements as this array. public func distinctUnion() -> AnyObservableSet { return DistinctUnion(self).anyObservableSet } } private class DistinctUnion: _BaseObservableSet where Input.Element: Hashable { typealias Element = Input.Element typealias Change = SetChange private struct DistinctSink: UniqueOwnedSink { typealias Owner = DistinctUnion unowned(unsafe) let owner: Owner func receive(_ update: ArrayUpdate) { owner.apply(update) } } private let input: Input private var members = Dictionary() init(_ input: Input) { self.input = input super.init() for element in input.value { _ = self.add(element) } input.updates.add(DistinctSink(owner: self)) } deinit { input.updates.remove(DistinctSink(owner: self)) } func apply(_ update: ArrayUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): var setChange = SetChange() for mod in change.modifications { mod.forEachOldElement { if remove($0) { setChange.remove($0) } } mod.forEachNewElement { if add($0) { setChange.insert($0) } } } if !setChange.isEmpty { sendChange(setChange) } case .endTransaction: endTransaction() } } private func add(_ element: Element) -> Bool { if let old = self.members[element] { self.members[element] = old + 1 return false } self.members[element] = 1 return true } private func remove(_ element: Element) -> Bool { let old = self.members[element]! if old == 1 { self.members[element] = nil return true } self.members[element] = old - 1 return false } override var isBuffered: Bool { return true } override var count: Int { return value.count } override var value: Set { return Set(members.keys) } override func contains(_ element: Element) -> Bool { return members[element] != nil } override func isSubset(of other: Set) -> Bool { guard count <= other.count else { return false } for (key, _) in members { guard other.contains(key) else { return false } } return true } override func isSuperset(of other: Set) -> Bool { guard count >= other.count else { return false } for element in other { guard members[element] != nil else { return false } } return true } } ================================================ FILE: Sources/DistinctValue.swift ================================================ // // DistinctValue.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-09. // Copyright © 2015–2017 Károly Lőrentey. // import SipHash private class DistinctSinkState { typealias Value = ValueUpdate let areEquivalent: (V, V) -> Bool var pending: ValueChange? = nil init(_ areEquivalent: @escaping (V, V) -> Bool) { self.areEquivalent = areEquivalent } fileprivate func applyUpdate(_ update: Value, _ sink: Sink) where Sink.Value == Value { switch update { case .beginTransaction: precondition(pending == nil) sink.receive(update) case .change(let change): if pending == nil { pending = change } else { pending!.merge(with: change) } case .endTransaction: if let change = pending, !areEquivalent(change.old, change.new) { sink.receive(.change(change)) } pending = nil sink.receive(update) } } } private struct DistinctSink: SinkType, SipHashable where Sink.Value == ValueUpdate { typealias Value = ValueUpdate let owner: AnyObject let sink: Sink let state: DistinctSinkState? func receive(_ update: Value) { state?.applyUpdate(update, sink) } func appendHashes(to hasher: inout SipHasher) { hasher.append(ObjectIdentifier(owner)) hasher.append(sink) } static func ==(left: DistinctSink, right: DistinctSink) -> Bool { return left.owner === right.owner && left.sink == right.sink } } public extension ObservableValueType { public func distinct(_ areEquivalent: @escaping (Value, Value) -> Bool) -> AnyObservableValue { return DistinctObservableValue(self, by: areEquivalent).anyObservableValue } } public extension ObservableValueType where Value: Equatable { public func distinct() -> AnyObservableValue { return distinct(==) } } private class DistinctObservableValue: _AbstractObservableValue { typealias Value = Input.Value private let input: Input private let areEquivalent: (Value, Value) -> Bool init(_ input: Input, by areEquivalent: @escaping (Value, Value) -> Bool) { self.input = input self.areEquivalent = areEquivalent } override var value: Value { return input.value } override func add(_ sink: Sink) where Sink.Value == Update { input.add(DistinctSink(owner: self, sink: sink, state: DistinctSinkState(areEquivalent))) } @discardableResult override func remove(_ sink: Sink) -> Sink where Sink.Value == Update { let old = input.remove(DistinctSink(owner: self, sink: sink, state: nil)) return old.sink } } public extension UpdatableValueType { public func distinct(_ areEquivalent: @escaping (Value, Value) -> Bool) -> AnyUpdatableValue { return DistinctUpdatableValue(self, by: areEquivalent).anyUpdatableValue } } public extension UpdatableValueType where Value: Equatable { public func distinct() -> AnyUpdatableValue { return distinct(==) } } private class DistinctUpdatableValue: _AbstractUpdatableValue { typealias Value = Input.Value private let input: Input private let areEquivalent: (Value, Value) -> Bool init(_ input: Input, by areEquivalent: @escaping (Value, Value) -> Bool) { self.input = input self.areEquivalent = areEquivalent } override var value: Value { get { return input.value } set { input.value = newValue } } override func apply(_ update: ValueUpdate) { input.apply(update) } override func add(_ sink: Sink) where Sink.Value == Update { input.add(DistinctSink(owner: self, sink: sink, state: DistinctSinkState(areEquivalent))) } @discardableResult override func remove(_ sink: Sink) -> Sink where Sink.Value == Update { let old = input.remove(DistinctSink(owner: self, sink: sink, state: nil)) return old.sink } } ================================================ FILE: Sources/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType FMWK CFBundleShortVersionString $(VERSION_STRING) CFBundleSignature ???? CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass ================================================ FILE: Sources/Locks.swift ================================================ // // Locks.swift // GlueKit // // Created by Károly Lőrentey on 2015-12-01. // Copyright © 2015–2017 Károly Lőrentey. // import Foundation internal protocol Lockable { func lock() func unlock() func withLock(_ block: () throws -> Result) rethrows -> Result } extension Lockable { func withLock(_ block: () throws -> Result) rethrows -> Result { lock() defer { unlock() } return try block() } } struct Lock: Lockable { private let _lock: LockImplementation init() { if #available(macOS 10.12, iOS 10, watchOS 3.0, tvOS 10.0, *) { self._lock = UnfairLock() } else { self._lock = PosixMutex() } } func lock() { _lock.lock() } func unlock() { _lock.unlock() } } private class LockImplementation: Lockable { init() {} func lock() {} func unlock() {} } @available(macOS 10.12, iOS 10, watchOS 3.0, tvOS 10.0, *) private final class UnfairLock: LockImplementation { private var _lock = os_unfair_lock() override func lock() { os_unfair_lock_lock(&_lock) } override func unlock() { os_unfair_lock_unlock(&_lock) } } private final class PosixMutex: LockImplementation { private var mutex = pthread_mutex_t() override init() { let result = pthread_mutex_init(&mutex, nil) precondition(result == 0) } deinit { let result = pthread_mutex_destroy(&mutex) precondition(result == 0) } override func lock() { let result = pthread_mutex_lock(&mutex) precondition(result == 0) } override func unlock() { let result = pthread_mutex_unlock(&mutex) precondition(result == 0) } } ================================================ FILE: Sources/MergedSource.swift ================================================ // // MergedSource.swift // GlueKit // // Created by Károly Lőrentey on 2015-12-04. // Copyright © 2015–2017 Károly Lőrentey. // import SipHash extension Sequence where Element: SourceType { public func gather() -> MergedSource { return MergedSource(sources: self) } } extension SourceType { /// Returns a source that merges self with `source`. The returned source will forward all values sent by either /// of its two input sources to its own connected sinks. /// /// It is fine to chain multiple merges together: `MergedSource` has its own, specialized `merge` method to /// collapse multiple merges into a single source. public func merged(with source: S) -> MergedSource where S.Value == Value { return MergedSource(sources: [self.anySource, source.anySource]) } public static func merge(_ sources: Self...) -> MergedSource { return MergedSource(sources: sources.map { s in s.anySource }) } public static func merge(_ sources: S) -> MergedSource where S.Iterator.Element == Self { return MergedSource(sources: sources.map { s in s.anySource }) } } /// A Source that receives all values from a set of input sources and forwards all to its own connected sinks. /// /// Note that MergedSource only connects to its input sources while it has at least one connection of its own. public final class MergedSource: SignalerSource { public typealias SourceValue = Value private let inputs: [AnySource] /// Initializes a new merged source with `sources` as its input sources. public init(sources: S) where S.Iterator.Element: SourceType, S.Iterator.Element.Value == Value { self.inputs = sources.map { $0.anySource } } override func activate() { for i in 0 ..< inputs.count { inputs[i].add(MergedSink(source: self, index: i)) } } override func deactivate() { for i in 0 ..< inputs.count { inputs[i].remove(MergedSink(source: self, index: i)) } } fileprivate func receive(_ value: Value, from index: Int) { signal.send(value) } /// Returns a new MergedSource that merges the same sources as self but also listens to `source`. /// The returned source will forward all values sent by either of its input sources to its own connected sinks. public func merged(with source: Source) -> MergedSource where Source.Value == Value { return MergedSource(sources: self.inputs + [source.anySource]) } } private struct MergedSink: SinkType, SipHashable { let source: MergedSource let index: Int func receive(_ value: Value) { source.receive(value, from: index) } func appendHashes(to hasher: inout SipHasher) { hasher.append(ObjectIdentifier(source)) hasher.append(index) } static func ==(left: MergedSink, right: MergedSink) -> Bool { return left.source === right.source && left.index == right.index } } ================================================ FILE: Sources/NSButton Glue.swift ================================================ // // NSButton Glue.swift // macOS // // Created by Károly Lőrentey on 2017-09-05. // Copyright © 2017 Károly Lőrentey. All rights reserved. // #if os(macOS) import AppKit extension NSButton { @objc open dynamic override var glue: GlueForNSButton { return _glue() } } public func <-- (target: GlueForNSButton.StateReceiver, model: V) where V.Value == NSControl.StateValue { target.glue.model = model.anyUpdatableValue } public func <-- (target: GlueForNSButton.StateReceiver, model: B) where B.Value == Bool { target.glue.model = model.map({ $0 ? .on : .off }, inverse: { $0 == .off ? false : true }) } open class GlueForNSButton: GlueForNSControl { private var object: NSButton { return owner as! NSButton } public struct StateReceiver { let glue: GlueForNSButton } public var state: StateReceiver { return StateReceiver(glue: self) } private let modelConnector = Connector() fileprivate var model: AnyUpdatableValue? { didSet { modelConnector.disconnect() if object.target === self { object.target = nil object.action = nil } if let model = model { object.target = self object.action = #selector(GlueForNSButton.buttonAction(_:)) modelConnector.connect(model.values) { [unowned self] value in self.object.state = value } } } } @IBAction func buttonAction(_ sender: NSButton) { self.model?.value = sender.state } } #endif ================================================ FILE: Sources/NSControl Glue.swift ================================================ // // NSControl Glue.swift // macOS // // Created by Károly Lőrentey on 2017-09-05. // Copyright © 2017 Károly Lőrentey. All rights reserved. // #if os(macOS) import AppKit extension NSControl { @objc open dynamic override var glue: GlueForNSControl { return _glue() } } public func <-- (target: GlueForNSControl.ValueSlot, model: Model) where Model.Value == Value { target.glue.setValueModel(model.anyUpdatableValue) } public func <-- (target: GlueForNSControl.ValueSlot, model: V) where V.Value == Value? { target.glue.setValueModel(model.anyUpdatableValue) } public func <-- (target: GlueForNSControl.ConfigSlot, model: Model) where Model.Value == Value { target.glue.setConfigSlot(target.keyPath, to: model.anyObservableValue) } open class GlueForNSControl: GlueForNSObject { private var object: NSControl { return owner as! NSControl } private var modelConnection: Connection? = nil fileprivate var valueModel: AnyObservableValue? = nil { didSet { modelConnection?.disconnect() if let model = valueModel { modelConnection = model.values.subscribe { [unowned self] value in self.object.objectValue = value } object.target = self object.action = #selector(GlueForNSControl.controlAction(_:)) } } } fileprivate var valueUpdater: ((Any?) -> Bool)? = nil fileprivate func setValueModel(_ model: AnyUpdatableValue) { self.valueUpdater = { value in guard let v = value as? V else { return false } model.value = v return true } self.valueModel = model.map { $0 as Any? } } fileprivate func setValueModel(_ model: AnyUpdatableValue) { self.valueUpdater = { value in model.value = value as? V return true } self.valueModel = model.map { $0 as Any? } } @objc func controlAction(_ sender: NSControl) { if valueUpdater?(sender.objectValue) != true { sender.objectValue = valueModel?.value } } public struct ValueSlot { fileprivate let glue: GlueForNSControl } public var intValue: ValueSlot { return ValueSlot(glue: self) } public var doubleValue: ValueSlot { return ValueSlot(glue: self) } public var stringValue: ValueSlot { return ValueSlot(glue: self) } public var attributedStringValue: ValueSlot { return ValueSlot(glue: self) } public struct ConfigSlot { fileprivate let glue: GlueForNSControl fileprivate let keyPath: ReferenceWritableKeyPath } var configModels: [AnyKeyPath: Connection] = [:] func setConfigSlot(_ keyPath: ReferenceWritableKeyPath, to model: AnyObservableValue) { let connection = model.values.subscribe { [unowned object] value in object[keyPath: keyPath] = value _ = object } configModels.updateValue(connection, forKey: keyPath)?.disconnect() } public var isEnabled: ConfigSlot { return ConfigSlot(glue: self, keyPath: \.isEnabled) } public var alignment: ConfigSlot { return ConfigSlot(glue: self, keyPath: \.alignment) } public var font: ConfigSlot { return ConfigSlot(glue: self, keyPath: \.font) } public var lineBreakMode: ConfigSlot { return ConfigSlot(glue: self, keyPath: \.lineBreakMode) } public var usesSingleLineMode: ConfigSlot { return ConfigSlot(glue: self, keyPath: \.usesSingleLineMode) } public var formatter: ConfigSlot { return ConfigSlot(glue: self, keyPath: \.formatter) } public var baseWritingDirection: ConfigSlot { return ConfigSlot(glue: self, keyPath: \.baseWritingDirection) } } #endif ================================================ FILE: Sources/NSNotificationCenter Support.swift ================================================ // // NSNotificationCenter Support.swift // GlueKit // // Created by Károly Lőrentey on 2015-11-30. // Copyright © 2015–2017 Károly Lőrentey. // import Foundation extension NotificationCenter { open override var glue: GlueForNotificationCenter { return _glue() } } open class GlueForNotificationCenter: GlueForNSObject { private var object: NotificationCenter { return owner as! NotificationCenter } /// Creates a Source that observes the specified notifications and forwards it to its connected sinks. /// /// The returned source holds strong references to the notification center and the sender (if any). /// The source will only observe the notification while a sink is actually connected. /// /// - Parameter name: The name of the notification to observe. /// - Parameter sender: The sender of the notifications to observe, or nil for any object. This parameter is nil by default. /// - Parameter queue: The operation queue on which the source will trigger. If you pass nil, the sinks are run synchronously on the thread that posted the notification. This parameter is nil by default. /// - Returns: A Source that triggers when the specified notification is posted. public func source(forName name: NSNotification.Name, sender: AnyObject? = nil, queue: OperationQueue? = nil) -> AnySource { return NotificationSource(center: object, name: name, sender: sender, queue: queue).anySource } } private class NotificationSource: SignalerSource { let center: NotificationCenter let name: NSNotification.Name let sender: AnyObject? let queue: OperationQueue? init(center: NotificationCenter, name: NSNotification.Name, sender: AnyObject?, queue: OperationQueue?) { self.center = center self.name = name self.sender = sender self.queue = queue super.init() } override func activate() { center.addObserver(self, selector: #selector(didReceive(_:)), name: name, object: sender) } override func deactivate() { center.removeObserver(self, name: name, object: sender) } @objc private func didReceive(_ notification: Notification) { if let queue = queue { queue.addOperation { self.signal.send(notification) } } else { self.signal.send(notification) } } } ================================================ FILE: Sources/NSObject Glue.swift ================================================ // // NSObject Glue.swift // GlueKit // // Created by Károly Lőrentey on 2016-04-11. // Copyright © 2015–2017 Károly Lőrentey. // import Foundation extension NSObjectProtocol where Self: NSObject { public func observable(for keyPath: KeyPath) -> AnyObservableValue { return ModernKVOObservable(self, keyPath).anyObservableValue } public func updatable(for keyPath: ReferenceWritableKeyPath) -> AnyUpdatableValue { return ModernKVOUpdatable(self, keyPath).anyUpdatableValue } } private class ModernKVOObservation: Hashable { typealias Sink = AnySink> let sink: Sink var observation: NSKeyValueObservation! var transactionCount: Int = 0 var pendingOld: Value? = nil var pendingNew: Value? = nil init(object: Root, keyPath: KeyPath, sink: Sink) { self.sink = sink.anySink self.observation = object.observe(keyPath, options: [.prior, .old, .new], changeHandler: self.observeChange) } var hashValue: Int { return sink.hashValue } public static func ==(left: ModernKVOObservation, right: ModernKVOObservation) -> Bool { return left.sink == right.sink } func invalidate() { observation.invalidate() if transactionCount > 0 { sink.receive(.endTransaction) } } func closeTransaction() { precondition(transactionCount > 0) guard transactionCount == 1 else { transactionCount -= 1; return } while let new = pendingNew { let old = pendingOld! pendingOld = new pendingNew = nil sink.receive(.change(.init(from: old, to: new))) precondition(transactionCount > 0) } transactionCount -= 1 if transactionCount == 0 { pendingOld = nil sink.receive(.endTransaction) } } func observeChange(object: Root, change: NSKeyValueObservedChange) { if change.isPrior { transactionCount += 1 if transactionCount == 1 { precondition(pendingOld == nil) // Weird round trip through Any is because change.oldValue/.newValue is nil if value is a nil optional. pendingOld = ((change.oldValue as Any) as! Value) sink.receive(.beginTransaction) } } else { precondition(transactionCount > 0) // Weird round trip through Any is because change.oldValue/.newValue is nil if value is a nil optional. pendingNew = ((change.newValue as Any) as! Value) closeTransaction() } } } private class ModernKVOObservable: _AbstractObservableValue { typealias Sink = AnySink> let object: Root let keyPath: KeyPath var sinks: [Sink: ModernKVOObservation] = [:] init(_ object: Root, _ keyPath: KeyPath) { self.object = object self.keyPath = keyPath } override var value: Value { return object[keyPath: keyPath] } override func add(_ sink: Sink) where Sink: SinkType, Sink.Value == Update { let r = sinks.updateValue(ModernKVOObservation(object: object, keyPath: keyPath, sink: sink.anySink), forKey: sink.anySink) precondition(r == nil) } override func remove(_ sink: Sink) -> Sink where Sink: SinkType, Sink.Value == Update { let (result, observation) = sinks.remove(at: sinks.index(forKey: sink.anySink)!) observation.invalidate() return result.opened()! } } private class ModernKVOUpdatable: _AbstractUpdatableValue { typealias Sink = AnySink> let object: Root let keyPath: ReferenceWritableKeyPath var sinks: [Sink: ModernKVOObservation] = [:] init(_ object: Root, _ keyPath: ReferenceWritableKeyPath) { self.object = object self.keyPath = keyPath } override var value: Value { get { return object[keyPath: keyPath] } set { object[keyPath: keyPath] = newValue } } override func apply(_ update: Update>) { switch update { case .beginTransaction: object.willChangeValue(for: keyPath) case .change(let change): object[keyPath: keyPath] = change.new case .endTransaction: object.didChangeValue(for: keyPath) } } override func add(_ sink: Sink) where Sink: SinkType, Sink.Value == Update { let r = sinks.updateValue(ModernKVOObservation(object: object, keyPath: keyPath, sink: sink.anySink), forKey: sink.anySink) precondition(r == nil) } override func remove(_ sink: Sink) -> Sink where Sink: SinkType, Sink.Value == Update { let (result, observation) = sinks.remove(at: sinks.index(forKey: sink.anySink)!) observation.invalidate() return result.opened()! } } // private var associatedObjectKeyForGlue: UInt8 = 0 extension NSObject { public func _glue() -> Glue { if let glue = objc_getAssociatedObject(self, &associatedObjectKeyForGlue) { return glue as! Glue } let glue = Glue(owner: self) objc_setAssociatedObject(self, &associatedObjectKeyForGlue, glue, .OBJC_ASSOCIATION_RETAIN) return glue } } extension NSObject { @objc open dynamic var glue: GlueForNSObject { return _glue() } } open class GlueForNSObject: NSObject { public unowned let owner: NSObject public private(set) lazy var connector = Connector() fileprivate var keyValueSources: [String: KVOSource] = [:] public required init(owner: NSObject) { self.owner = owner } } extension GlueForNSObject { // Key-Value Observing /// Returns an observable for the value of a KVO-compatible key path. /// Note that the object is retained by the returned source. public func observable(forKeyPath keyPath: String) -> KVOObservable { return KVOObservable(object: owner, keyPath: keyPath) } public func observable(forKeyPath keyPath: String, as type: T.Type = T.self) -> AnyObservableValue { return KVOObservable(object: owner, keyPath: keyPath).forceCasted() } public func observable(forKeyPath keyPath: String, as type: T?.Type = Optional.self) -> AnyObservableValue { return KVOObservable(object: owner, keyPath: keyPath).casted() } public func observable(forKeyPath keyPath: String, defaultValue: T) -> AnyObservableValue { return KVOObservable(object: owner, keyPath: keyPath).casted(defaultValue: defaultValue) } public func updatable(forKey key: String) -> KVOUpdatable { precondition(!key.contains("."), "Updatable key paths aren't supported; use GlueKit mappings instead") return KVOUpdatable(object: owner, key: key) } public func updatable(forKey key: String, as type: T.Type = T.self) -> AnyUpdatableValue { precondition(!key.contains("."), "Updatable key paths aren't supported; use GlueKit mappings instead") return KVOUpdatable(object: owner, key: key).forceCasted() } public func updatable(forKey key: String, as type: T?.Type = Optional.self) -> AnyUpdatableValue { precondition(!key.contains("."), "Updatable key paths aren't supported; use GlueKit mappings instead") return KVOUpdatable(object: owner, key: key).casted() } public func updatable(forKey key: String, defaultValue: T) -> AnyUpdatableValue { precondition(!key.contains("."), "Updatable key paths aren't supported; use GlueKit mappings instead") return KVOUpdatable(object: owner, key: key).casted(defaultValue: defaultValue) } } extension GlueForNSObject { fileprivate static var observingContext: UInt8 = 0 fileprivate func add(_ sink: Sink, forKeyPath keyPath: String) where Sink.Value == ValueUpdate { if let source = keyValueSources[keyPath] { source.add(sink) } else { let source = KVOSource(object: owner, keyPath: keyPath) keyValueSources[keyPath] = source source.add(sink) } } fileprivate func remove(_ sink: Sink, forKeyPath keyPath: String) -> Sink where Sink.Value == ValueUpdate { let source = keyValueSources[keyPath]! let old = source.remove(sink) return old } @objc open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if context == &GlueForNSObject.observingContext { keyValueSources[keyPath!]!.process(change!) } else { super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) } } } private final class KVOSource: TransactionalSource> { unowned let object: NSObject let keyPath: String init(object: NSObject, keyPath: String) { self.object = object self.keyPath = keyPath } override func activate() { object.addObserver(object.glue, forKeyPath: keyPath, options: [.old, .new, .prior], context: &GlueForNSObject.observingContext) } override func deactivate() { object.removeObserver(object.glue, forKeyPath: keyPath, context: &GlueForNSObject.observingContext) } func process(_ change: [NSKeyValueChangeKey : Any]) { if (change[.notificationIsPriorKey] as? NSNumber)?.boolValue == true { beginTransaction() } else { precondition(isInTransaction) let oldValue = change[.oldKey] let newValue = change[.newKey] let old: Any? = (oldValue is NSNull ? nil : oldValue) let new: Any? = (newValue is NSNull ? nil : newValue) let change = ValueChange(from: old, to: new) if isInOuterMostTransaction { sendChange(change) } endTransaction() } } } public struct KVOObservable: ObservableValueType { public typealias Change = ValueChange public let object: NSObject public let keyPath: String public var value: Any? { return object.value(forKeyPath: keyPath) } public func add(_ sink: Sink) where Sink.Value == Update { object.glue.add(sink, forKeyPath: keyPath) } public func remove(_ sink: Sink) -> Sink where Sink.Value == Update { return object.glue.remove(sink, forKeyPath: keyPath) } } public struct KVOUpdatable: UpdatableValueType { public typealias Change = ValueChange public let object: NSObject public let key: String public var value: Any? { get { return object.value(forKey: key) } nonmutating set { object.setValue(newValue, forKey: key) } } public func add(_ sink: Sink) where Sink.Value == Update { object.glue.add(sink, forKeyPath: key) } public func remove(_ sink: Sink) -> Sink where Sink.Value == Update { return object.glue.remove(sink, forKeyPath: key) } public func apply(_ update: Update>) { switch update { case .beginTransaction: object.willChangeValue(forKey: key) case .change(let change): object.setValue(change.new, forKey: key) case .endTransaction: object.didChangeValue(forKey: key) } } } ================================================ FILE: Sources/NSPopUpButton Glue.swift ================================================ // // NSPopUpButton Glue.swift // macOS // // Created by Károly Lőrentey on 2017-09-05. // Copyright © 2017 Károly Lőrentey. All rights reserved. // #if os(macOS) import AppKit extension NSPopUpButton { @objc open dynamic override var glue: GlueForNSPopUpButton { return _glue() } } public func <-- (target: GlueForNSPopUpButton, choices: NSPopUpButton.Choices) { target.setChoices(choices) } extension NSPopUpButton { public struct Choices { let model: AnyUpdatableValue let values: AnyObservableArray<(label: String, value: Value)> public init(model: U, values: C) where U.Value == Value, C.Element == (label: String, value: Value) { self.model = model.anyUpdatableValue self.values = values.anyObservableArray } public init(model: U, values: S) where U.Value == Value, S.Element == (label: String, value: Value) { self.model = model.anyUpdatableValue self.values = AnyObservableArray.constant(Array(values)) } public init(model: U, values: DictionaryLiteral) where U.Value == Value { self.model = model.anyUpdatableValue self.values = AnyObservableArray.constant(Array(values.map { ($0.key, $0.value) })) } } } open class GlueForNSPopUpButton: GlueForNSButton { private var object: NSPopUpButton { return owner as! NSPopUpButton } private var valueConnection: Connection? = nil private var choicesConnection: Connection? = nil private var update: (Any?) -> Void = { _ in } fileprivate func setChoices(_ choices: NSPopUpButton.Choices) { valueConnection?.disconnect() choicesConnection?.disconnect() update = { value in if let value = value as? Value { choices.model.value = value } } choicesConnection = choices.values.anyObservableValue.values.subscribe { [unowned self] choices in let menu = NSMenu() choices.forEach { choice in let item = NSMenuItem(title: choice.label, action: #selector(GlueForNSPopUpButton.choiceAction(_:)), keyEquivalent: "") item.target = self item.representedObject = choice.value menu.addItem(item) } self.object.menu = menu } valueConnection = choices.model.values.subscribe { [unowned self] newValue in if let item = self.object.menu?.items.first(where: { $0.representedObject as? Value == newValue }) { if self.object.selectedItem != item { self.object.select(item) } } else { self.object.select(nil) } } } @IBAction func choiceAction(_ sender: NSMenuItem) { update(sender.representedObject) } } #endif ================================================ FILE: Sources/NSTextField Glue.swift ================================================ // // NSTextField Glue.swift // macOS // // Created by Károly Lőrentey on 2017-09-05. // Copyright © 2017 Károly Lőrentey. All rights reserved. // #if os(macOS) import AppKit extension NSTextField { @objc open dynamic override var glue: GlueForNSTextField { return _glue() } } public func <-- (target: GlueForNSTextField.ValidatingValueReceiver, model: V) where V.Value: LosslessStringConvertible { target.glue.setModel(model) } open class GlueForNSTextField: GlueForNSControl { private var object: NSTextField { return owner as! NSTextField } private var delegate: Any? = nil public struct ValidatingValueReceiver { let glue: GlueForNSTextField } public var value: ValidatingValueReceiver { return ValidatingValueReceiver(glue: self) } fileprivate func setModel(_ model: V) where V.Value: LosslessStringConvertible { if let delegate = self.delegate as? GlueKitTextFieldDelegate { delegate.model = model.anyUpdatableValue } else { let delegate = GlueKitTextFieldDelegate(object, model) self.delegate = delegate } } } class GlueKitTextFieldDelegate: NSObject, NSTextFieldDelegate { unowned let view: NSTextField var model: AnyUpdatableValue { didSet { reconnect() } } init(_ view: NSTextField, _ model: V) where V.Value == Value { self.view = view self.model = model.anyUpdatableValue super.init() reconnect() } private var modelConnection: Connection? = nil private func reconnect() { view.delegate = self modelConnection?.disconnect() modelConnection = model.values.subscribe { [unowned self] value in self.view.stringValue = "\(value)" } } func control(_ control: NSControl, textShouldEndEditing fieldEditor: NSText) -> Bool { return Value(view.stringValue) != nil } override func controlTextDidEndEditing(_ obj: Notification) { if let value = Value(view.stringValue) { model.value = value } else { view.stringValue = "\(model.value)" } } func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool { guard commandSelector == #selector(NSResponder.cancelOperation(_:)) else { return false } view.stringValue = "\(model.value)" //textView.window?.makeFirstResponder(nil) return true } } #endif ================================================ FILE: Sources/ObservableArray.swift ================================================ // // AnyObservableArray.swift // GlueKit // // Created by Károly Lőrentey on 2015-12-11. // Copyright © 2015–2017 Károly Lőrentey. // public typealias ArrayUpdate = Update> public typealias ArrayUpdateSource = AnySource>> //MARK: ObservableArrayType /// An observable array type; i.e., a read-only, array-like observable collection that provides efficient change /// notifications. /// /// Changes to an observable array are broadcast as a sequence of `ArrayChange` values, which describe insertions, /// removals, and replacements. /// /// Any `ObservableArrayType` can be converted into a type-erased representation using `AnyObservableArray`. /// For a concrete observable array, see `ArrayVariable`. /// /// - SeeAlso: ObservableValueType, AnyObservableArray, UpdatableArrayType, ArrayVariable public protocol ObservableArrayType: ObservableType, CustomReflectable where Change == ArrayChange { associatedtype Element // Required methods var count: Int { get } subscript(bounds: Range) -> ArraySlice { get } // Extras var isBuffered: Bool { get } var value: [Element] { get } subscript(index: Int) -> Element { get } var observableCount: AnyObservableValue { get } var anyObservableValue: AnyObservableValue<[Element]> { get } var anyObservableArray: AnyObservableArray { get } } extension ObservableArrayType { public var isBuffered: Bool { return false } public var value: [Element] { return Array(self[0 ..< count]) } public subscript(_ index: Int) -> Element { return self[index ..< index + 1].first! } } extension ObservableArrayType { internal var valueUpdates: AnySource> { var value = self.value return self.updates.map { event in event.map { change in let old = value value.apply(change) return ValueChange(from: old, to: value) } }.buffered() } public var anyObservableValue: AnyObservableValue<[Element]> { return AnyObservableValue(getter: { self.value }, updates: self.valueUpdates) } public var observableCount: AnyObservableValue { return AnyObservableValue(getter: { self.count }, updates: self.updates.map { $0.map { $0.countChange } }) } public var anyObservableArray: AnyObservableArray { return AnyObservableArray(box: ObservableArrayBox(self)) } } extension ObservableArrayType { public var isEmpty: Bool { return count == 0 } public var first: Element? { guard count > 0 else { return nil } return self[0] } public var last: Element? { guard count > 0 else { return nil } return self[count - 1] } } extension ObservableArrayType { public var customMirror: Mirror { return Mirror(self, unlabeledChildren: self.value, displayStyle: .collection) } } /// An observable array type; i.e., a read-only, array-like `CollectionType` that also provides efficient change /// notifications. /// /// Changes to an observable array are broadcast as a sequence of `ArrayChange` values, which describe insertions, /// removals, and replacements. /// The count of elements in an `ObservableArrayType` is itself observable via its `observableCount` property. /// /// Any `ObservableArrayType` can be converted into a type-erased representation using `AnyObservableArray`. /// For a concrete observable array, see `ArrayVariable`. /// /// - SeeAlso: ObservableValueType, ObservableArrayType, UpdatableArrayType, ArrayVariable public struct AnyObservableArray: ObservableArrayType { public typealias Base = Array public typealias Change = ArrayChange let box: _AbstractObservableArray init(box: _AbstractObservableArray) { self.box = box } public init(_ array: A) where A.Element == Element { self = array.anyObservableArray } public var isBuffered: Bool { return box.isBuffered } public subscript(_ index: Int) -> Element { return box[index] } public subscript(_ range: Range) -> ArraySlice { return box[range] } public var value: Array { return box.value } public var count: Int { return box.count } public func add(_ sink: Sink) where Sink.Value == Update> { box.add(sink) } @discardableResult public func remove(_ sink: Sink) -> Sink where Sink.Value == Update> { return box.remove(sink) } public var observableCount: AnyObservableValue { return box.observableCount } public var anyObservableValue: AnyObservableValue<[Element]> { return box.anyObservableValue } public var anyObservableArray: AnyObservableArray { return self } } open class _AbstractObservableArray: ObservableArrayType { public typealias Base = Array public typealias Change = ArrayChange open var isBuffered: Bool { abstract() } open subscript(_ index: Int) -> Element { abstract() } open subscript(_ range: Range) -> ArraySlice { abstract() } open var value: Array { abstract() } open var count: Int { abstract() } open func add(_ sink: Sink) where Sink.Value == Update> { abstract() } @discardableResult open func remove(_ sink: Sink) -> Sink where Sink.Value == Update> { abstract() } open var observableCount: AnyObservableValue { return AnyObservableValue(getter: { self.count }, updates: self.updates.map { $0.map { $0.countChange } }) } open var anyObservableValue: AnyObservableValue<[Element]> { return AnyObservableValue(getter: { self.value }, updates: self.valueUpdates) } public final var anyObservableArray: AnyObservableArray { return AnyObservableArray(box: self) } } open class _BaseObservableArray: _AbstractObservableArray, TransactionalThing { var _signal: TransactionalSignal>? = nil var _transactionCount = 0 public final override func add(_ sink: Sink) where Sink.Value == Update> { signal.add(sink) } @discardableResult public final override func remove(_ sink: Sink) -> Sink where Sink.Value == Update> { return signal.remove(sink) } open func activate() { // Do nothing } open func deactivate() { // Do nothing } } internal final class ObservableArrayBox: _AbstractObservableArray { typealias Element = Contents.Element let contents: Contents init(_ Contents: Contents) { self.contents = Contents } override var isBuffered: Bool { return contents.isBuffered } override subscript(_ index: Int) -> Element { return contents[index] } override subscript(_ range: Range) -> ArraySlice { return contents[range] } override var value: Array { return contents.value } override var count: Int { return contents.count } override func add(_ sink: Sink) where Sink.Value == Update> { contents.add(sink) } @discardableResult override func remove(_ sink: Sink) -> Sink where Sink.Value == Update> { return contents.remove(sink) } override var observableCount: AnyObservableValue { return contents.observableCount } override var anyObservableValue: AnyObservableValue<[Element]> { return contents.anyObservableValue } } internal final class ObservableArrayConstant: _AbstractObservableArray { let _value: Array init(_ value: [Element]) { self._value = value } override var isBuffered: Bool { return true } override subscript(_ index: Int) -> Element { return _value[index] } override subscript(_ range: Range) -> ArraySlice { return _value[range] } override var value: Array { return _value } override var count: Int { return _value.count } override func add(_ sink: Sink) where Sink.Value == Update> { // Do nothing } @discardableResult override func remove(_ sink: Sink) -> Sink where Sink.Value == Update> { return sink } override var observableCount: AnyObservableValue { return AnyObservableValue.constant(_value.count) } override var anyObservableValue: AnyObservableValue<[Element]> { return AnyObservableValue.constant(_value) } } extension ObservableArrayType { public static func constant(_ value: [Element]) -> AnyObservableArray { return ObservableArrayConstant(value).anyObservableArray } public static func emptyConstant() -> AnyObservableArray { return constant([]) } } ================================================ FILE: Sources/ObservableContains.swift ================================================ // // ObservableContains.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-02. // Copyright © 2015–2017 Károly Lőrentey. // extension ObservableSetType { public func observableContains(_ member: Element) -> AnyObservableValue { return ObservableContains(input: self, member: member).anyObservableValue } } private final class ObservableContains: _AbstractObservableValue { let input: Input let member: Input.Element let _updates: AnySource> init(input: Input, member: Input.Element) { self.input = input self.member = member self._updates = input.updates.flatMap { update in update.flatMap { let old = $0.removed.contains(member) let new = $0.inserted.contains(member) if old == new { return nil } else { return ValueChange(from: old, to: new) } } } } override var value: Bool { return input.contains(member) } override func add(_ sink: Sink) where Sink.Value == Update { _updates.add(sink) } @discardableResult override func remove(_ sink: Sink) -> Sink where Sink.Value == Update { return _updates.remove(sink) } } ================================================ FILE: Sources/ObservableSet.swift ================================================ // // ObservableSet.swift // GlueKit // // Created by Károly Lőrentey on 2016-08-12. // Copyright © 2015–2017 Károly Lőrentey. // public typealias SetUpdate = Update> public typealias SetUpdateSource = AnySource>> public protocol ObservableSetType: ObservableType where Change == SetChange { associatedtype Element typealias Base = Set var isBuffered: Bool { get } var count: Int { get } var value: Set { get } func contains(_ member: Element) -> Bool func isSubset(of other: Set) -> Bool func isSuperset(of other: Set) -> Bool var observableCount: AnyObservableValue { get } var anyObservableValue: AnyObservableValue { get } var anyObservableSet: AnyObservableSet { get } } extension ObservableSetType { public var isBuffered: Bool { return false } public var count: Int { return value.count } public func contains(_ member: Element) -> Bool { return value.contains(member) } public func isSubset(of other: Set) -> Bool { return value.isSubset(of: other) } public func isSuperset(of other: Set) -> Bool { return value.isSuperset(of: other) } public var isEmpty: Bool { return count == 0 } internal var valueUpdates: AnySource>> { var value = self.value return self.updates.map { event in event.map { change in let old = value value.apply(change) return ValueChange(from: old, to: value) } }.buffered() } internal var countUpdates: AnySource> { var count = self.count return self.updates.map { update in update.map { change in let old = count count += numericCast(change.inserted.count - change.removed.count) return .init(from: old, to: count) } }.buffered() } public var observableCount: AnyObservableValue { return AnyObservableValue(getter: { self.count }, updates: self.countUpdates) } public var anyObservableValue: AnyObservableValue { return AnyObservableValue(getter: { self.value }, updates: self.valueUpdates) } public var anyObservableSet: AnyObservableSet { return AnyObservableSet(box: ObservableSetBox(self)) } } public struct AnyObservableSet: ObservableSetType { public typealias Base = Set public typealias Change = SetChange let box: _AbstractObservableSet init(box: _AbstractObservableSet) { self.box = box } public var isBuffered: Bool { return box.isBuffered } public var count: Int { return box.count } public var value: Set { return box.value } public func contains(_ member: Element) -> Bool { return box.contains(member) } public func isSubset(of other: Set) -> Bool { return box.isSubset(of: other) } public func isSuperset(of other: Set) -> Bool { return box.isSuperset(of: other) } public func add(_ sink: Sink) where Sink.Value == Update { box.add(sink) } @discardableResult public func remove(_ sink: Sink) -> Sink where Sink.Value == Update { return box.remove(sink) } public var observableCount: AnyObservableValue { return box.observableCount } public var anyObservableValue: AnyObservableValue> { return box.anyObservableValue } public var anyObservableSet: AnyObservableSet { return self } } open class _AbstractObservableSet: ObservableSetType { public typealias Change = SetChange open var value: Set { abstract() } open func add(_ sink: Sink) where Sink.Value == Update> { abstract() } @discardableResult open func remove(_ sink: Sink) -> Sink where Sink.Value == Update> { abstract() } open var isBuffered: Bool { return false } open var count: Int { return value.count } open func contains(_ member: Element) -> Bool { return value.contains(member) } open func isSubset(of other: Set) -> Bool { return value.isSubset(of: other) } open func isSuperset(of other: Set) -> Bool { return value.isSuperset(of: other) } open var observableCount: AnyObservableValue { return AnyObservableValue(getter: { self.count }, updates: self.countUpdates) } open var anyObservableValue: AnyObservableValue> { return AnyObservableValue(getter: { self.value }, updates: self.valueUpdates) } public final var anyObservableSet: AnyObservableSet { return AnyObservableSet(box: self) } } open class _BaseObservableSet: _AbstractObservableSet, TransactionalThing { var _signal: TransactionalSignal>? = nil var _transactionCount = 0 public final override func add(_ sink: Sink) where Sink.Value == Update> { signal.add(sink) } @discardableResult public final override func remove(_ sink: Sink) -> Sink where Sink.Value == Update> { return signal.remove(sink) } func activate() { // Do nothing } func deactivate() { // Do nothing } } final class ObservableSetBox: _AbstractObservableSet { typealias Element = Contents.Element let contents: Contents init(_ contents: Contents) { self.contents = contents } override var isBuffered: Bool { return contents.isBuffered } override var count: Int { return contents.count } override var value: Set { return contents.value } override func contains(_ member: Element) -> Bool { return contents.contains(member) } override func isSubset(of other: Set) -> Bool { return contents.isSubset(of: other) } override func isSuperset(of other: Set) -> Bool { return contents.isSuperset(of: other) } override func add(_ sink: Sink) where Sink.Value == Update> { contents.add(sink) } @discardableResult override func remove(_ sink: Sink) -> Sink where Sink.Value == Update> { return contents.remove(sink) } override var observableCount: AnyObservableValue { return contents.observableCount } override var anyObservableValue: AnyObservableValue> { return contents.anyObservableValue } } class ObservableConstantSet: _AbstractObservableSet { let contents: Set init(_ contents: Set) { self.contents = contents } override var isBuffered: Bool { return true } override var count: Int { return contents.count } override var value: Set { return contents } override func contains(_ member: Element) -> Bool { return contents.contains(member) } override func isSubset(of other: Set) -> Bool { return contents.isSubset(of: other) } override func isSuperset(of other: Set) -> Bool { return contents.isSuperset(of: other) } override func add(_ sink: Sink) where Sink.Value == Update> { // Do nothing } @discardableResult override func remove(_ sink: Sink) -> Sink where Sink.Value == Update> { return sink } override var observableCount: AnyObservableValue { return AnyObservableValue.constant(contents.count) } override var anyObservableValue: AnyObservableValue> { return AnyObservableValue.constant(contents) } } extension ObservableSetType { public static func constant(_ value: Set) -> AnyObservableSet { return ObservableConstantSet(value).anyObservableSet } public static func emptyConstant() -> AnyObservableSet { return constant([]) } } ================================================ FILE: Sources/ObservableType.swift ================================================ // // ObservableValueType.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-04. // Copyright © 2015–2017 Károly Lőrentey. // public protocol ObservableType { associatedtype Value associatedtype Change: ChangeType where Change.Value == Value /// The current value of this observable. var value: Value { get } func add(_ sink: Sink) where Sink.Value == Update @discardableResult func remove(_ sink: Sink) -> Sink where Sink.Value == Update } extension ObservableType { /// A source that reports update transaction events for this observable. public var updates: UpdateSource { return UpdateSource(owner: self) } } extension ObservableType { /// A source that sends an empty value whenever the observable completes a transaction. public var tick: AnySource { return self.updates.flatMap { if case .endTransaction = $0 { return () }; return nil } } } public struct UpdateSource: SourceType { public typealias Value = Update private let owner: Observable init(owner: Observable) { self.owner = owner } public func add(_ sink: Sink) where Sink.Value == Value { owner.add(sink) } @discardableResult public func remove(_ sink: Sink) -> Sink where Sink.Value == Value { return owner.remove(sink) } } public protocol UpdatableType: ObservableType { /// The current value of this observable. /// /// The setter is nonmutating because the value ultimately needs to be stored in a reference type anyway. var value: Value { get nonmutating set } func apply(_ update: Update) } extension UpdatableType { public func withTransaction(_ body: () -> Result) -> Result { apply(.beginTransaction) defer { apply(.endTransaction) } return body() } public func apply(_ change: Change) { if !change.isEmpty { apply(.beginTransaction) apply(.change(change)) apply(.endTransaction) } } } extension ObservableType { public func subscribe(to updatable: Updatable) -> Connection where Updatable.Change == Change { updatable.apply(.beginTransaction) updatable.apply(.change(Change(from: updatable.value, to: self.value))) let connection = updates.subscribe { update in updatable.apply(update) } updatable.apply(.endTransaction) return connection } } extension Connector { @discardableResult public func subscribe(_ observable: Observable, to sink: @escaping (Update) -> Void) -> Connection { return observable.updates.subscribe(sink).putInto(self) } } ================================================ FILE: Sources/ObservableValue.swift ================================================ // // Observable.swift // GlueKit // // Created by Károly Lőrentey on 2015-12-07. // Copyright © 2015–2017 Károly Lőrentey. // public typealias ValueUpdate = Update> /// An observable has a value that is readable at any time, and may change in response to certain events. /// Interested parties can sign up to receive notifications when the observable's value changes. /// /// In GlueKit, observables are represented by types implementing `ObservableValueType`. They provide update notifications /// via either of two sources: /// /// - `values` sends the initial value of the observable to each new sink, followed by the values of later updates. /// - `futureValues` skips the initial value and just sends values on future updates. /// /// The simplest concrete observable is `Variable`, implementing a settable variable with an individual observable value. /// `ArrayVariable` implements an observable array of values, with efficient change notifications. /// /// If you have one or more observables, you can use GlueKit's rich set of observable transformations and compositions /// to build observable expressions out of them. /// /// Types implementing `ObservableValueType` are generally not type-safe; you must serialize all accesses to them /// (including connecting to any of their sources). /// public protocol ObservableValueType: ObservableType, CustomPlaygroundQuickLookable where Change == ValueChange { /// Returns the type-erased version of this ObservableValueType. var anyObservableValue: AnyObservableValue { get } } extension ObservableValueType { /// Returns the type-erased version of this ObservableValueType. public var anyObservableValue: AnyObservableValue { return AnyObservableValue(self) } /// A source that delivers new values whenever this observable changes. public var futureValues: AnySource { return changes.map { $0.new } } /// A source that, for each new sink, immediately sends it the current value, and thereafter delivers updated values, /// like `futureValues`. Implemented in terms of `futureValues` and `value`. public var values: AnySource { return futureValues.bracketed(hello: { self.value }, goodbye: { nil }) } } extension ObservableValueType { public var customPlaygroundQuickLook: PlaygroundQuickLook { return PlaygroundQuickLook.text("\(value)") } } /// The type erased representation of an ObservableValueType that contains a single value with simple changes. public struct AnyObservableValue: ObservableValueType { public typealias Change = ValueChange private let box: _AbstractObservableValue init(box: _AbstractObservableValue) { self.box = box } /// Initializes an Observable from the given getter closure and source of future changes. /// @param getter A closure that returns the current value of the observable at the time of the call. /// @param futureValues A closure that returns a source that triggers whenever the observable changes. public init(getter: @escaping () -> Value, updates: Updates) where Updates.Value == Update { self.box = ObservableClosureBox(getter: getter, updates: updates) } public init(_ base: Base) where Base.Value == Value { self.box = ObservableValueBox(base) } public var value: Value { return box.value } public func add(_ sink: Sink) where Sink.Value == Update { box.add(sink) } @discardableResult public func remove(_ sink: Sink) -> Sink where Sink.Value == Update { return box.remove(sink) } public var anyObservableValue: AnyObservableValue { return self } } open class _AbstractObservableValue: ObservableValueType { public typealias Change = ValueChange open var value: Value { abstract() } open func add(_ sink: Sink) where Sink.Value == Update { abstract() } @discardableResult open func remove(_ sink: Sink) -> Sink where Sink.Value == Update { abstract() } public final var anyObservableValue: AnyObservableValue { return AnyObservableValue(box: self) } } open class _BaseObservableValue: _AbstractObservableValue, TransactionalThing { public typealias Change = ValueChange var _signal: TransactionalSignal>? = nil var _transactionCount: Int = 0 public final override func add(_ sink: Sink) where Sink.Value == Update { signal.add(sink) } @discardableResult public final override func remove(_ sink: Sink) -> Sink where Sink.Value == Update { return signal.remove(sink) } open func activate() { // Do nothing } open func deactivate() { // Do nothing } } internal final class ObservableValueBox: _AbstractObservableValue { typealias Value = Base.Value private let base: Base init(_ base: Base) { self.base = base } override var value: Value { return base.value } override func add(_ sink: Sink) where Sink.Value == Update { base.add(sink) } @discardableResult override func remove(_ sink: Sink) -> Sink where Sink.Value == Update { return base.remove(sink) } } private final class ObservableClosureBox: _AbstractObservableValue where Updates.Value == Update> { private let _value: () -> Value private let _updates: Updates public init(getter: @escaping () -> Value, updates: Updates) { self._value = getter self._updates = updates } override var value: Value { return _value() } override func add(_ sink: Sink) where Sink.Value == Update { _updates.add(sink) } @discardableResult override func remove(_ sink: Sink) -> Sink where Sink.Value == Update { return _updates.remove(sink) } } public extension ObservableValueType { /// Creates a constant observable wrapping the given value. The returned observable is not modifiable and it will not ever send updates. public static func constant(_ value: Value) -> AnyObservableValue { return ConstantObservable(value).anyObservableValue } } private final class ConstantObservable: _AbstractObservableValue { private let _value: Value init(_ value: Value) { _value = value } override var value: Value { return _value } override func add(_ sink: Sink) where Sink.Value == Update { // Do nothing } @discardableResult override func remove(_ sink: Sink) -> Sink where Sink.Value == Update { // Do nothing return sink } } ================================================ FILE: Sources/OwnedSink.swift ================================================ // // StrongMethodSink.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-24. // Copyright © 2015–2017 Károly Lőrentey. // import SipHash protocol UniqueOwnedSink: SinkType { associatedtype Owner: AnyObject var owner: Owner { get } } extension UniqueOwnedSink { var hashValue: Int { return ObjectIdentifier(owner).hashValue } static func ==(left: Self, right: Self) -> Bool { return left.owner === right.owner } } protocol OwnedSink: SinkType, SipHashable { associatedtype Owner: AnyObject associatedtype Identifier: Hashable var owner: Owner { get } var identifier: Identifier { get } } extension OwnedSink { func appendHashes(to hasher: inout SipHasher) { hasher.append(ObjectIdentifier(owner)) hasher.append(identifier) } static func ==(left: Self, right: Self) -> Bool { return left.owner === right.owner && left.identifier == right.identifier } } ================================================ FILE: Sources/RefList.swift ================================================ // // RefList.swift // GlueKit // // Created by Károly Lőrentey on 2016-09-26. // Copyright © 2015–2017 Károly Lőrentey. // /// An element in a reflist, with an opaque link to its parent node. protocol RefListElement: class { /// An opaque link to the element's parent node in the ref list. var refListLink: RefListLink { get set } } internal struct RefListLink { fileprivate var _parent: UnownedReference>? internal init() { self._parent = nil } } extension RefListElement { fileprivate var parent: RefListNode? { get { return refListLink._parent?.value } set { if let p = newValue { refListLink._parent = UnownedReference(p) } else { refListLink._parent = nil } } } } /// A reflist is a B-tree backed random-access list data structure. It does not support copy-on-write mutation, /// but it supports parent links, allowing for efficient determination of any element's position in the list in O(log(n)) time. /// Elements in the refList may only be in a single list at a time. internal final class RefList: RandomAccessCollection, MutableCollection, RangeReplaceableCollection { fileprivate typealias Node = RefListNode internal typealias Index = Int internal typealias Indices = CountableRange internal typealias Iterator = IndexingIterator fileprivate var root: Node required convenience init() { self.init(order: Node.defaultOrder) } required convenience init(_ elements: S) where S.Iterator.Element == Element { // TODO: Implement bulk loader self.init() self.append(contentsOf: elements) } init(order: Int) { self.root = Node(order: order) } internal var count: Int { return root.count } internal var startIndex: Int { return 0 } internal var endIndex: Int { return count } internal subscript(index: Int) -> Element { get { let (node, slot) = self.slot(of: index) return node.elements[slot] } set { precondition(newValue.parent == nil) let (node, slot) = self.slot(of: index) let old = node.elements[slot] node.elements[slot] = newValue newValue.parent = node old.parent = nil } } internal subscript(bounds: Range) -> MutableRangeReplaceableRandomAccessSlice { get { return .init(base: self, bounds: bounds) } set { // Elements can only belong to a single RefList, but slices contain elements that are already in one. fatalError("RefList does not support range replacement.") } } internal func forEach(in range: Range? = nil, body: (Element) throws -> ()) rethrows { if let range = range, range != 0 ..< count { try root.forEach(range, body) } else { try root.forEach(body) } } internal func index(of element: Element) -> Int? { var node = element.parent! var offset = node.offset(of: element) while let parent = node.parent { offset += parent.offset(of: node) node = parent } precondition(node === root) return offset } private func slot(of offset: Int) -> (node: Node, slot: Int) { precondition(offset >= 0 && offset < count) var offset = offset var node = root while !node.isLeaf { let slot = node.slot(atOffset: offset) if slot.match { return (node, slot.index) } let child = node.children[slot.index] offset -= slot.offset - child.count node = child } return (node, offset) } internal func insert(_ element: Element, at index: Int) { precondition(element.parent == nil) precondition(index >= 0 && index <= count) var pos = count - index var splinter: (separator: Element, node: Node)? = nil var element = element root.edit( descend: { node in let slot = node.slot(atOffset: node.count - pos) if !slot.match { // Continue descending. pos -= node.count - slot.offset return slot.index } if node.isLeaf { // Found the insertion point. Insert, then start ascending. node.insert(element, inSlot: slot.index) if node.isTooLarge { splinter = node.split() } return nil } // For internal nodes, put the new element in place of the old at the same offset, // then continue descending toward the next offset, inserting the old element. element = node.setElement(inSlot: slot.index, to: element) pos = node.children[slot.index + 1].count return slot.index + 1 }, ascend: { node, slot in node.count += 1 if let s = splinter { s.separator.parent = node s.node.parent = node node.elements.insert(s.separator, at: slot) node.children.insert(s.node, at: slot + 1) splinter = node.isTooLarge ? node.split() : nil } } ) if let s = splinter { root = Node(left: root, separator: s.separator, right: s.node) } assert(element.parent != nil) } internal func insert(contentsOf newElements: C, at index: Int) where C.Iterator.Element == Iterator.Element { // TODO: Implement bulk insertion using join. var i = index for element in newElements { self.insert(element, at: i) i += 1 } } internal func append(_ newElement: Element) { self.insert(newElement, at: count) } internal func append(contentsOf newElements: S) where S.Iterator.Element == Iterator.Element { // TODO: Implement bulk insertion using join. var i = count for element in newElements { self.insert(element, at: i) i += 1 } } /// Remove and return the element at the specified offset. /// /// - Note: When you need to perform multiple modifications on the same tree, /// `BTreeCursor` provides an alternative interface that's often more efficient. /// - Complexity: O(log(`count`)) @discardableResult internal func remove(at index: Int) -> Element { precondition(index >= 0 && index < count) var pos = count - index var matching: (node: Node, slot: Int)? = nil var old: Element? = nil root.edit( descend: { node in let slot = node.slot(atOffset: node.count - pos) if !slot.match { // No match yet; continue descending. assert(!node.isLeaf) pos -= node.count - slot.offset return slot.index } if node.isLeaf { // The offset we're looking for is in a leaf node; we can remove it directly. old = node.elements.remove(at: slot.index) old!.parent = nil node.count -= 1 return nil } // When the offset happens to fall in an internal node, remember the match and continue // removing the next offset (which is guaranteed to be in a leaf node). // We'll replace the removed element with this one during the ascend. matching = (node, slot.index) pos = node.children[slot.index + 1].count return slot.index + 1 }, ascend: { node, slot in node.count -= 1 if let m = matching, m.node === node { // We've removed the element at the next offset; put it back in place of the // element we actually want to remove. old!.parent = node old = node.setElement(inSlot: m.slot, to: old!) old!.parent = nil matching = nil } if node.children[slot].isTooSmall { node.fixDeficiency(slot) } } ) if root.children.count == 1 { assert(root.elements.count == 0) root = root.children[0] root.parent = nil } precondition(old?.parent == nil) return old! } internal func removeSubrange(_ bounds: Range) { // TODO: Make this more efficient. for index in CountableRange(bounds).reversed() { self.remove(at: index) } } internal func replaceSubrange(_ subrange: Range, with newElements: C) where C.Iterator.Element == Element { // TODO: Make this more efficient. self.removeSubrange(subrange) self.insert(contentsOf: newElements, at: subrange.lowerBound) } } fileprivate final class RefListNode { typealias Node = RefListNode var _parent: UnownedReference? var elements: [Element] var children: [RefListNode] var count: Int = 0 let order: Int var depth: Int static var defaultOrder: Int { return Swift.max(16383 / MemoryLayout.stride, 31) } init(order: Int, elements: [Element], children: [Node], count: Int) { precondition(elements.count <= order) precondition(children.count == 0 || children.count == elements.count + 1) self._parent = nil self.elements = elements self.children = children self.count = count self.order = order self.depth = (children.count == 0 ? 0 : children[0].depth + 1) elements.forEach { $0.parent = self } children.forEach { $0.parent = self } } convenience init(order: Int = RefListNode.defaultOrder) { self.init(order: order, elements: [], children: [], count: 0) } convenience init(left: Node, separator: Element, right: Node) { precondition(left.order == right.order && left.depth == right.depth) self.init(order: left.order, elements: [separator], children: [left, right], count: left.count + 1 + right.count) } convenience init(node: Node, slotRange: CountableRange) { if node.isLeaf { let elements = Array(node.elements[slotRange]) self.init(order: node.order, elements: elements, children: [], count: elements.count) } else if slotRange.count == 0 { let n = node.children[slotRange.lowerBound] self.init(order: n.order, elements: n.elements, children: n.children, count: n.count) } else { let elements = Array(node.elements[slotRange]) let children = Array(node.children[slotRange.lowerBound ... slotRange.upperBound]) let count = children.reduce(elements.count) { $0 + $1.count } self.init(order: node.order, elements: elements, children: children, count: count) } } var parent: RefListNode? { get { return _parent?.value } set { if let p = newValue { _parent = UnownedReference(p) } else { _parent = nil } } } var maxChildren: Int { return order } var minChildren: Int { return (maxChildren + 1) / 2 } var maxElements: Int { return maxChildren - 1 } var minElements: Int { return minChildren - 1 } var isLeaf: Bool { return depth == 0 } var isTooSmall: Bool { return elements.count < minElements } var isTooLarge: Bool { return elements.count > maxElements } var isBalanced: Bool { return elements.count >= minElements && elements.count <= maxElements } } extension RefListNode { func edit(descend: (Node) -> Int?, ascend: (Node, Int) -> Void) { guard let slot = descend(self) else { return } let child = children[slot] child.edit(descend: descend, ascend: ascend) ascend(self, slot) } func setElement(inSlot slot: Int, to element: Element) -> Element { let old = elements[slot] elements[slot] = element element.parent = self old.parent = nil return old } func insert(_ element: Element, inSlot slot: Int) { elements.insert(element, at: slot) count += 1 element.parent = self } func offset(of element: Element) -> Int { if isLeaf { return elements.index { $0 === element }! } var offset = 0 var found = false for i in 0 ..< elements.count { offset += children[i].count if elements[i] === element { found = true; break } offset += 1 } precondition(found) return offset } func offset(of child: Node) -> Int { var offset = 0 var found = false for c in children { if c === child { found = true; break } offset += 1 + c.count } precondition(found) return offset } /// Return the slot of the element at `offset` in the subtree rooted at this node. func slot(atOffset offset: Int) -> (index: Int, match: Bool, offset: Int) { assert(offset >= 0 && offset <= count) if offset == count { return (index: elements.count, match: isLeaf, offset: count) } if isLeaf { return (offset, true, offset) } else if offset <= count / 2 { var p = 0 for i in 0 ..< children.count - 1 { let c = children[i].count if offset == p + c { return (index: i, match: true, offset: p + c) } if offset < p + c { return (index: i, match: false, offset: p + c) } p += c + 1 } let c = children.last!.count precondition(count == p + c, "Invalid B-Tree") return (index: children.count - 1, match: false, offset: count) } var p = count for i in (1 ..< children.count).reversed() { let c = children[i].count if offset == p - (c + 1) { return (index: i - 1, match: true, offset: offset) } if offset > p - (c + 1) { return (index: i, match: false, offset: p) } p -= c + 1 } let c = children.first!.count precondition(p - c == 0, "Invalid B-Tree") return (index: 0, match: false, offset: c) } /// Split this node into two, removing the high half of the nodes and putting them in a splinter. /// /// - Returns: A splinter consisting of a separator and a node containing the higher half of the original node. func split() -> (separator: Element, node: Node) { assert(isTooLarge) return split(at: elements.count / 2) } /// Split this node into two at the key at index `median`, removing all elements at or above `median` /// and putting them in a splinter. /// /// - Returns: A splinter consisting of a separator and a node containing the higher half of the original node. func split(at median: Int) -> (separator: Element, node: Node) { let count = elements.count let separator = elements[median] let splinter = Node(node: self, slotRange: median + 1 ..< count) elements.removeSubrange(median ..< count) if isLeaf { self.count = median } else { children.removeSubrange(median + 1 ..< count + 1) self.count = median + children.reduce(0) { $0 + $1.count } } separator.parent = nil return (separator, splinter) } /// Reorganize the tree rooted at `self` so that the undersize child in `slot` is corrected. /// As a side effect of the process, `self` may itself become undersized, but all of its descendants /// become balanced. func fixDeficiency(_ slot: Int) { assert(!isLeaf && children[slot].isTooSmall) if slot > 0 && children[slot - 1].elements.count > minElements { rotateRight(slot) } else if slot < children.count - 1 && children[slot + 1].elements.count > minElements { rotateLeft(slot) } else if slot > 0 { // Collapse deficient slot into previous slot. collapse(slot - 1) } else { // Collapse next slot into deficient slot. collapse(slot) } } func rotateRight(_ slot: Int) { assert(slot > 0) let previous = children[slot - 1] let child = children[slot] let e = elements[slot - 1] child.elements.insert(e, at: 0) e.parent = child if !child.isLeaf { let lastGrandChildBeforeSlot = previous.children.removeLast() lastGrandChildBeforeSlot.parent = child child.children.insert(lastGrandChildBeforeSlot, at: 0) previous.count -= lastGrandChildBeforeSlot.count child.count += lastGrandChildBeforeSlot.count } let element = previous.elements.removeLast() element.parent = self elements[slot - 1] = element previous.count -= 1 child.count += 1 } func rotateLeft(_ slot: Int) { assert(slot < children.count - 1) let child = children[slot] let next = children[slot + 1] let e = elements[slot] e.parent = child child.elements.append(e) if !child.isLeaf { let firstGrandChildAfterSlot = next.children.remove(at: 0) firstGrandChildAfterSlot.parent = child child.children.append(firstGrandChildAfterSlot) next.count -= firstGrandChildAfterSlot.count child.count += firstGrandChildAfterSlot.count } let element = next.elements.remove(at: 0) element.parent = self elements[slot] = element child.count += 1 next.count -= 1 } func collapse(_ slot: Int) { assert(slot < children.count - 1) let target = children[slot] let collapsed = children.remove(at: slot + 1) collapsed.parent = nil let e = elements.remove(at: slot) e.parent = target target.elements.append(e) target.count += 1 collapsed.elements.forEach { $0.parent = target } target.elements.append(contentsOf: collapsed.elements) target.count += collapsed.count if !collapsed.isLeaf { collapsed.children.forEach { $0.parent = target } target.children.append(contentsOf: collapsed.children) } assert(target.isBalanced) } } extension RefListNode { func forEach(_ body: (Element) throws -> ()) rethrows { if isLeaf { try elements.forEach(body) } else { for i in 0 ..< elements.count { try children[i].forEach(body) try body(elements[i]) } try children[elements.count].forEach(body) } } func forEach(_ range: Range, _ body: (Element) throws -> ()) rethrows { if isLeaf { for element in elements[range] { try body(element) } return } var c = range.count let slot = self.slot(atOffset: range.lowerBound) guard range.count > 0 else { return } if !slot.match { let child = children[slot.index] let childCount = child.count let childStartOffset = slot.offset - childCount let childRange: Range = range.lowerBound - childStartOffset ..< min(childCount, range.upperBound - childStartOffset) try child.forEach(childRange, body) c -= childRange.count } var index = slot.index while c > 0 { try body(elements[index]) c -= 1 guard c > 0 else { break } index += 1 let child = children[index] if c >= child.count { try child.forEach(body) c -= child.count } else { try child.forEach(0 ..< c, body) c = 0 } } } } ================================================ FILE: Sources/Reference.swift ================================================ // // Reference.swift // GlueKit // // Created by Károly Lőrentey on 2015-12-13. // Copyright © 2015–2017 Károly Lőrentey. // internal struct UnownedReference { unowned var value: Target init(_ value: Target) { self.value = value } } ================================================ FILE: Sources/SetChange.swift ================================================ // // SetChange.swift // GlueKit // // Created by Károly Lőrentey on 2016-08-12. // Copyright © 2015–2017 Károly Lőrentey. // public struct SetChange: ChangeType { public typealias Value = Set public private(set) var removed: Set public private(set) var inserted: Set public init(removed: Set = [], inserted: Set = []) { self.inserted = inserted self.removed = removed } public init(from oldValue: Value, to newValue: Value) { self.removed = oldValue.subtracting(newValue) self.inserted = newValue.subtracting(oldValue) } public var isEmpty: Bool { return inserted.isEmpty && removed.isEmpty } public func apply(on value: inout Set) { value.subtract(removed) value = inserted.union(value) } public func apply(on value: Value) -> Value { return inserted.union(value.subtracting(removed)) } public mutating func remove(_ element: Element) { self.inserted.remove(element) self.removed.update(with: element) } public mutating func insert(_ element: Element) { self.inserted.update(with: element) } public mutating func merge(with next: SetChange) { removed = next.removed.union(removed) inserted = next.inserted.union(inserted.subtracting(next.removed)) } public func merged(with next: SetChange) -> SetChange { return SetChange(removed: next.removed.union(removed), inserted: next.inserted.union(inserted.subtracting(next.removed))) } public func reversed() -> SetChange { return SetChange(removed: inserted, inserted: removed) } public func removingEqualChanges() -> SetChange { let intersection = removed.intersection(inserted) if intersection.isEmpty { return self } return SetChange(removed: removed.subtracting(intersection), inserted: inserted.subtracting(intersection)) } } extension Set { public mutating func apply(_ change: SetChange) { self.subtract(change.removed) for e in change.inserted { self.update(with: e) } } } ================================================ FILE: Sources/SetFilteringOnObservableBool.swift ================================================ // // SetFilteringOnObservableBool.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-07. // Copyright © 2015–2017 Károly Lőrentey. // import SipHash extension ObservableSetType { public func filter(_ isIncluded: @escaping (Element) -> Field) -> AnyObservableSet where Field.Value == Bool { return SetFilteringOnObservableBool(parent: self, isIncluded: isIncluded).anyObservableSet } } private class SetFilteringOnObservableBool: _BaseObservableSet where Field.Value == Bool { typealias Element = Parent.Element typealias Change = SetChange private struct ParentSink: UniqueOwnedSink { typealias Owner = SetFilteringOnObservableBool unowned(unsafe) let owner: Owner func receive(_ update: SetUpdate) { owner.applyParentUpdate(update) } } private struct FieldSink: SinkType, SipHashable { typealias Owner = SetFilteringOnObservableBool unowned(unsafe) let owner: Owner let element: Parent.Element func receive(_ update: ValueUpdate) { owner.applyFieldUpdate(update, from: element) } func appendHashes(to hasher: inout SipHasher) { hasher.append(ObjectIdentifier(owner)) hasher.append(element) } static func ==(left: FieldSink, right: FieldSink) -> Bool { return left.owner === right.owner && left.element == right.element } } private let parent: Parent private let isIncluded: (Element) -> Field private var matchingElements: Set = [] private var fieldSinks: Dictionary = [:] init(parent: Parent, isIncluded: @escaping (Element) -> Field) { self.parent = parent self.isIncluded = isIncluded } override var isBuffered: Bool { return false } override var count: Int { if isConnected { return matchingElements.count } var count = 0 for element in parent.value { if isIncluded(element).value { count += 1 } } return count } override var value: Set { if isConnected { return matchingElements } return Set(self.parent.value.filter { isIncluded($0).value }) } override func contains(_ member: Element) -> Bool { if isConnected { return matchingElements.contains(member) } return self.parent.contains(member) && isIncluded(member).value } override func isSubset(of other: Set) -> Bool { if isConnected { return matchingElements.isSubset(of: other) } for member in self.parent.value { guard isIncluded(member).value else { continue } guard other.contains(member) else { return false } } return true } override func isSuperset(of other: Set) -> Bool { if isConnected { return matchingElements.isSuperset(of: other) } for member in other { guard isIncluded(member).value && parent.contains(member) else { return false } } return true } override func activate() { for e in parent.value { let test = self.isIncluded(e) if test.value { matchingElements.insert(e) } let sink = FieldSink(owner: self, element: e) test.add(sink) fieldSinks[sink] = test } parent.add(ParentSink(owner: self)) } override func deactivate() { parent.remove(ParentSink(owner: self)) for (sink, test) in fieldSinks { test.remove(sink) } fieldSinks = [:] matchingElements = [] } func applyParentUpdate(_ update: SetUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): var c = SetChange() for e in change.removed { let sink = FieldSink(owner: self, element: e) let test = fieldSinks.removeValue(forKey: sink)! test.remove(sink) if let old = self.matchingElements.remove(e) { c.remove(old) } } for e in change.inserted { let test = self.isIncluded(e) let sink = FieldSink(owner: self, element: e) test.add(sink) let old = fieldSinks.updateValue(test, forKey: sink) precondition(old == nil) if test.value { matchingElements.insert(e) c.insert(e) } } if !c.isEmpty { sendChange(c) } case .endTransaction: endTransaction() } } func applyFieldUpdate(_ update: ValueUpdate, from element: Parent.Element) { switch update { case .beginTransaction: beginTransaction() case .change(let change): if !change.old && change.new { matchingElements.insert(element) sendChange(SetChange(inserted: [element])) } else if change.old && !change.new { matchingElements.remove(element) sendChange(SetChange(removed: [element])) } case .endTransaction: endTransaction() } } } ================================================ FILE: Sources/SetFilteringOnPredicate.swift ================================================ // // SetFilteringOnPredicate.swift // GlueKit // // Created by Károly Lőrentey on 2015-12-12. // Copyright © 2015–2017 Károly Lőrentey. // extension ObservableSetType { public func filter(_ isIncluded: @escaping (Element) -> Bool) -> AnyObservableSet { return SetFilteringOnPredicate(parent: self, test: isIncluded).anyObservableSet } public func filter(_ isIncluded: Predicate) -> AnyObservableSet where Predicate.Value == (Element) -> Bool { return self.filter(isIncluded.map { predicate -> Optional<(Element) -> Bool> in predicate }) } public func filter(_ isIncluded: Predicate) -> AnyObservableSet where Predicate.Value == Optional<(Element) -> Bool> { let reference: AnyObservableValue> = isIncluded.map { predicate in if let predicate: (Element) -> Bool = predicate { return self.filter(predicate).anyObservableSet } else { return self.anyObservableSet } } return reference.unpacked() } } private final class SetFilteringOnPredicate: _BaseObservableSet { public typealias Element = Parent.Element public typealias Change = SetChange private struct FilteringSink: UniqueOwnedSink { typealias Owner = SetFilteringOnPredicate unowned let owner: Owner func receive(_ update: SetUpdate) { owner.applyParentUpdate(update) } } private let parent: Parent private let test: (Element) -> Bool private var matchingElements: Set = [] init(parent: Parent, test: @escaping (Element) -> Bool) { self.parent = parent self.test = test } override var isBuffered: Bool { return false } override var count: Int { if isConnected { return matchingElements.count } return parent.value.reduce(0) { test($1) ? $0 + 1 : $0 } } override var value: Set { if isConnected { return matchingElements } return Set(self.parent.value.filter(test)) } override func contains(_ member: Element) -> Bool { if isConnected { return matchingElements.contains(member) } return self.parent.contains(member) && test(member) } override func isSubset(of other: Set) -> Bool { if isConnected { return matchingElements.isSubset(of: other) } for member in self.parent.value { guard test(member) else { continue } guard other.contains(member) else { return false } } return true } override func isSuperset(of other: Set) -> Bool { if isConnected { return matchingElements.isSuperset(of: other) } for member in other { guard test(member) && parent.contains(member) else { return false } } return true } override func activate() { for e in parent.value { if test(e) { matchingElements.insert(e) } } parent.add(FilteringSink(owner: self)) } override func deactivate() { parent.remove(FilteringSink(owner: self)) matchingElements = [] } func applyParentUpdate(_ update: SetUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): var c = SetChange() for e in change.removed { if let old = matchingElements.remove(e) { c.remove(old) } } for e in change.inserted { if self.test(e) { matchingElements.insert(e) c.insert(e) } } if !c.isEmpty { sendChange(c) } case .endTransaction: endTransaction() } } } ================================================ FILE: Sources/SetFolding.swift ================================================ // // SetFolding.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-09. // Copyright © 2015–2017 Károly Lőrentey. // extension ObservableSetType { /// Returns an observable whose value is always equal to `self.value.reduce(initial, add)`. /// /// - Parameter initial: The accumulation starts with this initial value. /// - Parameter add: A closure that adds an element of the set into an accumulated value. /// - Parameter remove: A closure that cancels the effect of an earlier `add`. /// - Returns: An observable value for the reduction of this set. /// /// - Note: Elements are added and removed in no particular order. /// (I.e., the underlying binary operation over `Result` must form an abelian group.) /// /// - SeeAlso: `sum()` which returns a reduction using addition. public func reduce(_ initial: Result, add: @escaping (Result, Element) -> Result, remove: @escaping (Result, Element) -> Result) -> AnyObservableValue { return SetFoldingByTwoWayFunction(parent: self, initial: initial, add: add, remove: remove).anyObservableValue } } extension ObservableSetType where Element: BinaryInteger { /// Return the (observable) sum of the elements contained in this set. public func sum() -> AnyObservableValue { return reduce(0, add: +, remove: -) } } private class SetFoldingByTwoWayFunction: _BaseObservableValue { private struct FoldingSink: UniqueOwnedSink { typealias Owner = SetFoldingByTwoWayFunction unowned(unsafe) let owner: Owner func receive(_ update: SetUpdate) { owner.applyUpdate(update) } } let parent: Parent let add: (Value, Parent.Element) -> Value let remove: (Value, Parent.Element) -> Value private var _value: Value init(parent: Parent, initial: Value, add: @escaping (Value, Parent.Element) -> Value, remove: @escaping (Value, Parent.Element) -> Value) { self.parent = parent self.add = add self.remove = remove self._value = parent.value.reduce(initial, add) super.init() parent.add(FoldingSink(owner: self)) } deinit { parent.remove(FoldingSink(owner: self)) } override var value: Value { return _value } func applyUpdate(_ update: SetUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): let old = _value for old in change.removed { _value = remove(_value, old) } for new in change.inserted { _value = add(_value, new) } sendChange(ValueChange(from: old, to: _value)) case .endTransaction: endTransaction() } } } ================================================ FILE: Sources/SetGatheringSource.swift ================================================ // // SetGatheringSource.swift // GlueKit // // Created by Károly Lőrentey on 2017-09-05. // Copyright © 2017 Károly Lőrentey. All rights reserved. // extension ObservableSetType where Element: SourceType { public func gather() -> AnySource { return SetGatheringSource(self).anySource } } private class SetGatheringSource: _AbstractSource where Origin.Element: SourceType, Origin.Element.Value == Value { let origin: Origin var sinks: Set> = [] private struct GatherSink: UniqueOwnedSink { typealias Owner = SetGatheringSource unowned let owner: Owner func receive(_ value: SetUpdate) { guard case let .change(change) = value else { return } change.removed.forEach { source in for sink in owner.sinks { source.remove(sink) } } change.inserted.forEach { source in for sink in owner.sinks { source.add(sink) } } } } init(_ origin: Origin) { self.origin = origin } override func add(_ sink: Sink) where Sink.Value == Value { if sinks.isEmpty { origin.add(GatherSink(owner: self)) } let new = sinks.insert(sink.anySink).inserted precondition(new) for source in origin.value { source.add(sink) } } @discardableResult override func remove(_ sink: Sink) -> Sink where Sink.Value == Value { let result = sinks.remove(sink.anySink)! for source in origin.value { source.remove(result) } if sinks.isEmpty { origin.remove(GatherSink(owner: self)) } return result.opened()! } } ================================================ FILE: Sources/SetMappingBase.swift ================================================ // // SetMappingBase.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-05. // Copyright © 2015–2017 Károly Lőrentey. // /// An observable set where the value is internally represented as a dictionary of element multiplicities. /// This class implements the full `ObservableSetType` protocol, and serves as the base class for several transformations /// on observable sets. class SetMappingBase: _BaseObservableSet { typealias Change = SetChange private(set) var contents: [Element: Int] = [:] /// Insert `newMember` into `state`, and return true iff it did not previously contain it. final func insert(_ newMember: Element) -> Bool { if let count = contents[newMember] { contents[newMember] = count + 1 return false } contents[newMember] = 1 return true } /// Remove a single instance of `newMember` from `state`, and return true iff this was the last instance. /// - Requires: `self.contains(member)`. final func remove(_ member: Element) -> Bool { guard let count = contents[member] else { fatalError("Inconsistent change: \(member) to be removed is not in result set") } if count > 1 { contents[member] = count - 1 return false } contents.removeValue(forKey: member) return true } final override var isBuffered: Bool { return false } final override var count: Int { return contents.count } final override var value: Set { return Set(contents.keys) } final override func contains(_ member: Element) -> Bool { return contents[member] != nil } final override func isSubset(of other: Set) -> Bool { guard other.count >= contents.count else { return false } for (key, _) in contents { guard other.contains(key) else { return false } } return true } final override func isSuperset(of other: Set) -> Bool { guard other.count <= contents.count else { return false } for element in other { guard contents[element] != nil else { return false } } return true } } ================================================ FILE: Sources/SetMappingForArrayField.swift ================================================ // // SetMappingForArrayField.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-07. // Copyright © 2015–2017 Károly Lőrentey. // extension ObservableSetType { public func flatMap(_ key: @escaping (Element) -> Field) -> AnyObservableSet where Field.Element: Hashable { return SetMappingForArrayField(parent: self, key: key).anyObservableSet } } class SetMappingForArrayField: SetMappingBase where Field.Element: Hashable { private struct ParentSink: UniqueOwnedSink { typealias Owner = SetMappingForArrayField unowned(unsafe) let owner: Owner func receive(_ update: SetUpdate) { owner.applyParentUpdate(update) } } private struct FieldSink: UniqueOwnedSink { typealias Owner = SetMappingForArrayField unowned(unsafe) let owner: Owner func receive(_ update: ArrayUpdate) { owner.applyFieldUpdate(update) } } let parent: Parent let key: (Parent.Element) -> Field init(parent: Parent, key: @escaping (Parent.Element) -> Field) { self.parent = parent self.key = key super.init() parent.add(ParentSink(owner: self)) for e in parent.value { let field = key(e) field.add(FieldSink(owner: self)) for new in field.value { _ = self.insert(new) } } } deinit { parent.remove(ParentSink(owner: self)) for e in parent.value { let field = key(e) field.remove(FieldSink(owner: self)) } } func applyParentUpdate(_ update: SetUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): var transformedChange = SetChange() for e in change.removed { let field = key(e) field.remove(FieldSink(owner: self)) for r in field.value { if self.remove(r) { transformedChange.remove(r) } } } for e in change.inserted { let field = key(e) field.add(FieldSink(owner: self)) for i in field.value { if self.insert(i) { transformedChange.insert(i) } } } if !transformedChange.isEmpty { sendChange(transformedChange) } case .endTransaction: endTransaction() } } func applyFieldUpdate(_ update: ArrayUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): var transformedChange = SetChange() change.forEachOld { old in if self.remove(old) { transformedChange.remove(old) } } change.forEachNew { new in if self.insert(new) { transformedChange.insert(new) } } transformedChange = transformedChange.removingEqualChanges() if !transformedChange.isEmpty { sendChange(transformedChange) } case .endTransaction: endTransaction() } } } ================================================ FILE: Sources/SetMappingForSequence.swift ================================================ // // SetMappingForSequence.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-07. // Copyright © 2015–2017 Károly Lőrentey. // extension ObservableSetType { public func flatMap(_ key: @escaping (Element) -> Result) -> AnyObservableSet where Result.Iterator.Element: Hashable { return SetMappingForSequence(parent: self, key: key).anyObservableSet } } class SetMappingForSequence: SetMappingBase where Result.Iterator.Element: Hashable { typealias Element = Result.Iterator.Element private struct ParentSink: UniqueOwnedSink { typealias Owner = SetMappingForSequence unowned(unsafe) let owner: Owner func receive(_ update: SetUpdate) { owner.apply(update) } } let parent: Parent let key: (Parent.Element) -> Result init(parent: Parent, key: @escaping (Parent.Element) -> Result) { self.parent = parent self.key = key super.init() for e in parent.value { for new in key(e) { _ = self.insert(new) } } parent.add(ParentSink(owner: self)) } deinit { parent.remove(ParentSink(owner: self)) } func apply(_ update: SetUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): var transformedChange = SetChange() for e in change.removed { for old in key(e) { if self.remove(old) { transformedChange.remove(old) } } } for e in change.inserted { for new in key(e) { if self.insert(new) { transformedChange.insert(new) } } } if !transformedChange.isEmpty { sendChange(transformedChange) } case .endTransaction: endTransaction() } } } ================================================ FILE: Sources/SetMappingForSetField.swift ================================================ // // SetMappingForSetField.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-07. // Copyright © 2015–2017 Károly Lőrentey. // extension ObservableSetType { public func flatMap(_ key: @escaping (Element) -> Field) -> AnyObservableSet { return SetMappingForSetField(parent: self, key: key).anyObservableSet } } class SetMappingForSetField: SetMappingBase { private struct ParentSink: UniqueOwnedSink { typealias Owner = SetMappingForSetField unowned(unsafe) let owner: Owner func receive(_ update: SetUpdate) { owner.applyParentUpdate(update) } } private struct FieldSink: UniqueOwnedSink { typealias Owner = SetMappingForSetField unowned(unsafe) let owner: Owner func receive(_ update: SetUpdate) { owner.applyFieldUpdate(update) } } let parent: Parent let key: (Parent.Element) -> Field init(parent: Parent, key: @escaping (Parent.Element) -> Field) { self.parent = parent self.key = key super.init() parent.add(ParentSink(owner: self)) for e in parent.value { let field = key(e) field.add(FieldSink(owner: self)) for new in field.value { _ = self.insert(new) } } } deinit { parent.remove(ParentSink(owner: self)) parent.value.forEach { e in let field = key(e) field.remove(FieldSink(owner: self)) } } func applyParentUpdate(_ update: SetUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): var transformedChange = SetChange() for e in change.removed { let field = key(e) field.remove(FieldSink(owner: self)) for r in field.value { if self.remove(r) { transformedChange.remove(r) } } } for e in change.inserted { let field = key(e) field.add(FieldSink(owner: self)) for i in field.value { if self.insert(i) { transformedChange.insert(i) } } } if !transformedChange.isEmpty { sendChange(transformedChange) } case .endTransaction: endTransaction() } } func applyFieldUpdate(_ update: SetUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): var transformedChange = SetChange() for old in change.removed { if self.remove(old) { transformedChange.remove(old) } } for new in change.inserted { if self.insert(new) { transformedChange.insert(new) } } if !transformedChange.isEmpty { sendChange(transformedChange) } case .endTransaction: endTransaction() } } } ================================================ FILE: Sources/SetMappingForValue.swift ================================================ // // SetMappingForValue.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-05. // Copyright © 2015–2017 Károly Lőrentey. // extension ObservableSetType { /// Return an observable set that contains the results of injectively mapping the given closure over the elements of this set. /// /// - Parameter transform: A mapping closure. `transform` must be an injection; if it maps two nonequal elements into /// the same result, the transformation may trap or it may return invalid results. /// /// - SeeAlso: `map(_:)` for a slightly slower variant for use when the mapping is not injective. public func injectiveMap(_ transform: @escaping (Element) -> R) -> AnyObservableSet { return InjectiveSetMappingForValue(parent: self, transform: transform).anyObservableSet } } private final class InjectiveSetMappingForValue: _BaseObservableSet { typealias Change = SetChange private struct InjectiveSink: UniqueOwnedSink { typealias Owner = InjectiveSetMappingForValue unowned(unsafe) let owner: Owner func receive(_ update: SetUpdate) { owner.apply(update) } } let parent: Parent let transform: (Parent.Element) -> Element private var _value: Set = [] init(parent: Parent, transform: @escaping (Parent.Element) -> Element) { self.parent = parent self.transform = transform super.init() _value = Set(parent.value.map(transform)) parent.add(InjectiveSink(owner: self)) } deinit { parent.remove(InjectiveSink(owner: self)) } func apply(_ update: SetUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): let mappedChange = SetChange(removed: Set(change.removed.lazy.map(transform)), inserted: Set(change.inserted.lazy.map(transform))) precondition(mappedChange.removed.count == change.removed.count, "injectiveMap: transformation is not injective; use map() instead") precondition(mappedChange.inserted.count == change.inserted.count, "injectiveMap: transformation is not injective; use map() instead") _value.apply(mappedChange) if !mappedChange.isEmpty { sendChange(mappedChange) } case .endTransaction: endTransaction() } } override var isBuffered: Bool { return true } override var count: Int { return parent.count } override var value: Set { return _value } override func contains(_ member: Element) -> Bool { return _value.contains(member) } override func isSubset(of other: Set) -> Bool { return _value.isSubset(of: other) } override func isSuperset(of other: Set) -> Bool { return _value.isSuperset(of: other) } } extension ObservableSetType { /// Return an observable set that contains the results of mapping the given closure over the elements of this set. /// /// - Parameter transform: A mapping closure. `transform` does not need to be an injection; elements where /// `transform` returns the same result will be collapsed into a single entry in the result set. /// /// - SeeAlso: `injectivelyMap(_:)` for a slightly faster variant for when the mapping is injective. public func map(_ transform: @escaping (Element) -> R) -> AnyObservableSet { return SetMappingForValue(parent: self, transform: transform).anyObservableSet } } private final class SetMappingForValue: SetMappingBase { typealias Change = SetChange private struct MapSink: UniqueOwnedSink { typealias Owner = SetMappingForValue unowned(unsafe) let owner: Owner func receive(_ update: SetUpdate) { owner.apply(update) } } let parent: Parent let transform: (Parent.Element) -> Element private var connection: Connection? = nil init(parent: Parent, transform: @escaping (Parent.Element) -> Element) { self.parent = parent self.transform = transform super.init() for element in parent.value { _ = self.insert(transform(element)) } parent.updates.add(MapSink(owner: self)) } deinit { parent.updates.remove(MapSink(owner: self)) } func apply(_ update: SetUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): var mappedChange = SetChange() for element in change.removed { let transformed = transform(element) if self.remove(transformed) { mappedChange.remove(transformed) } } for element in change.inserted { let transformed = transform(element) if self.insert(transformed) { mappedChange.insert(transformed) } } if !mappedChange.isEmpty { sendChange(mappedChange) } case .endTransaction: endTransaction() } } } ================================================ FILE: Sources/SetMappingForValueField.swift ================================================ // // SetMappingForValueField.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-07. // Copyright © 2015–2017 Károly Lőrentey. // extension ObservableSetType { /// Given an observable set and a closure that extracts an observable value from each element, /// return an observable set that contains the extracted field values contained in this set. /// /// - Parameter key: A mapping closure, extracting an observable value from an element of this set. public func map(_ key: @escaping (Element) -> Field) -> AnyObservableSet where Field.Value: Hashable { return SetMappingForValueField(parent: self, key: key).anyObservableSet } } class SetMappingForValueField: SetMappingBase where Field.Value: Hashable { private struct ParentSink: UniqueOwnedSink { typealias Owner = SetMappingForValueField unowned(unsafe) let owner: Owner func receive(_ update: SetUpdate) { owner.applyParentUpdate(update) } } private struct FieldSink: UniqueOwnedSink { typealias Owner = SetMappingForValueField unowned(unsafe) let owner: Owner func receive(_ update: ValueUpdate) { owner.applyFieldUpdate(update) } } let parent: Parent let key: (Parent.Element) -> Field init(parent: Parent, key: @escaping (Parent.Element) -> Field) { self.parent = parent self.key = key super.init() parent.add(ParentSink(owner: self)) for e in parent.value { let field = key(e) field.add(FieldSink(owner: self)) _ = self.insert(field.value) } } deinit { parent.remove(ParentSink(owner: self)) for e in parent.value { let field = key(e) field.remove(FieldSink(owner: self)) } } func applyParentUpdate(_ update: SetUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): var transformedChange = SetChange() for e in change.removed { let field = key(e) let value = field.value field.remove(FieldSink(owner: self)) if self.remove(value) { transformedChange.remove(value) } } for e in change.inserted { let field = key(e) let value = field.value field.add(FieldSink(owner: self)) if self.insert(value) { transformedChange.insert(value) } } if !transformedChange.isEmpty { sendChange(transformedChange) } case .endTransaction: endTransaction() } } func applyFieldUpdate(_ update: ValueUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): if change.old == change.new { return } var transformedChange = SetChange() if self.remove(change.old) { transformedChange.remove(change.old) } if self.insert(change.new) { transformedChange.insert(change.new) } if !transformedChange.isEmpty { sendChange(transformedChange) } case .endTransaction: endTransaction() } } } ================================================ FILE: Sources/SetReference.swift ================================================ // // SetReference.swift // GlueKit // // Created by Károly Lőrentey on 2016-08-17. // Copyright © 2015–2017 Károly Lőrentey. // extension ObservableValueType where Value: ObservableSetType { public func unpacked() -> AnyObservableSet { return UnpackedObservableSetReference(self).anyObservableSet } } /// A mutable reference to an `AnyObservableSet` that's also an observable set. /// You can switch to another target set without having to re-register subscribers. private final class UnpackedObservableSetReference: _BaseObservableSet where Reference.Value: ObservableSetType { typealias Target = Reference.Value typealias Element = Target.Element typealias Change = SetChange private struct ReferenceSink: UniqueOwnedSink { typealias Owner = UnpackedObservableSetReference unowned(unsafe) let owner: Owner func receive(_ update: ValueUpdate) { owner.applyReferenceUpdate(update) } } private struct TargetSink: UniqueOwnedSink { typealias Owner = UnpackedObservableSetReference unowned(unsafe) let owner: Owner func receive(_ update: SetUpdate) { owner.applyTargetUpdate(update) } } private var _reference: Reference private var _target: Reference.Value? = nil // Retained to make sure we keep it alive init(_ reference: Reference) { _reference = reference super.init() } override func activate() { _reference.add(ReferenceSink(owner: self)) let target = _reference.value _target = target target.add(TargetSink(owner: self)) } override func deactivate() { _target!.remove(TargetSink(owner: self)) _reference.remove(ReferenceSink(owner: self)) } func applyReferenceUpdate(_ update: ValueUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): if isConnected { _target!.remove(TargetSink(owner: self)) _target = change.new _target!.add(TargetSink(owner: self)) sendChange(SetChange(from: change.old.value, to: change.new.value)) } case .endTransaction: endTransaction() } } func applyTargetUpdate(_ update: SetUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): sendChange(change) case .endTransaction: endTransaction() } } override var isBuffered: Bool { return false } override var count: Int { return _reference.value.count } override var value: Set { return _reference.value.value } override func contains(_ member: Element) -> Bool { return _reference.value.contains(member) } override func isSubset(of other: Set) -> Bool { return _reference.value.isSubset(of: other) } override func isSuperset(of other: Set) -> Bool { return _reference.value.isSuperset(of: other) } } ================================================ FILE: Sources/SetSortingByComparableField.swift ================================================ // // SetSortingByMappingToObservableComparable.swift // GlueKit // // Created by Károly Lőrentey on 2017-05-01. // Copyright © 2015–2017 Károly Lőrentey. // import SipHash import BTree extension ObservableSetType where Element: AnyObject { /// Given a transformation into an observable of a comparable type, return an observable array /// containing transformed versions of elements in this set, in increasing order. public func sorted(by transform: @escaping (Element) -> Field) -> AnyObservableArray where Field.Value: Comparable { return SetSortingByComparableField(parent: self, transform: transform).anyObservableArray } } private class SetSortingByComparableField: _BaseObservableArray where Parent.Element: AnyObject, Field.Value: Comparable { typealias Element = Parent.Element typealias Change = ArrayChange private struct ParentSink: UniqueOwnedSink { typealias Owner = SetSortingByComparableField unowned(unsafe) let owner: Owner func receive(_ update: SetUpdate) { owner.applyParentUpdate(update) } } private struct FieldSink: SinkType, SipHashable { typealias Owner = SetSortingByComparableField unowned(unsafe) let owner: Owner let element: Parent.Element func receive(_ update: ValueUpdate) { owner.applyFieldUpdate(update, from: element) } func appendHashes(to hasher: inout SipHasher) { hasher.append(ObjectIdentifier(owner)) hasher.append(element) } static func ==(left: FieldSink, right: FieldSink) -> Bool { return left.owner === right.owner && left.element == right.element } } private let parent: Parent private let transform: (Parent.Element) -> Field private var contents: BTree = .init() private var fields: Dictionary = [:] init(parent: Parent, transform: @escaping (Parent.Element) -> Field) { self.parent = parent self.transform = transform super.init() for element in parent.value { let key = newElement(element) _ = self.insert(key, element) } parent.add(ParentSink(owner: self)) } deinit { parent.remove(ParentSink(owner: self)) for (sink, field) in fields { field.remove(sink) } } private func newElement(_ element: Parent.Element) -> Field.Value { let field = transform(element) let sink = FieldSink(owner: self, element: element) let old = fields.updateValue(field, forKey: sink) field.add(sink) precondition(old == nil) return field.value } private func removeElement(_ element: Parent.Element) { let sink = FieldSink(owner: self, element: element) let field = fields.removeValue(forKey: sink)! field.remove(sink) } private func insert(_ key: Field.Value, _ element: Element) -> ArrayModification { return contents.withCursor(onKey: key, choosing: .after) { cursor in let offset = cursor.offset cursor.insert((key, element)) return .insert(element, at: offset) } } private func remove(_ key: Field.Value, _ element: Element) -> ArrayModification { return contents.withCursor(onKey: key, choosing: .first) { cursor in while cursor.value !== element { cursor.moveForward() precondition(!cursor.isAtEnd, "Inconsistent change: element removed is not in sorted set") } let offset = cursor.offset return .remove(element, at: offset) } } func applyParentUpdate(_ update: SetUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): var arrayChange = ArrayChange(initialCount: contents.count) for element in change.removed { let key = transform(element).value removeElement(element) arrayChange.add(self.remove(key, element)) } for element in change.inserted { let key = newElement(element) arrayChange.add(self.insert(key, element)) } if !arrayChange.isEmpty { sendChange(arrayChange) } case .endTransaction: endTransaction() } } func applyFieldUpdate(_ update: ValueUpdate, from element: Parent.Element) { switch update { case .beginTransaction: beginTransaction() case .change(let change): var arrayChange = ArrayChange(initialCount: self.contents.count) if change.old == change.new { return } arrayChange.add(remove(change.old, element)) arrayChange.add(insert(change.new, element)) if !arrayChange.isEmpty { sendChange(arrayChange) } case .endTransaction: endTransaction() } } override var isBuffered: Bool { return false } override subscript(index: Int) -> Element { return contents.element(atOffset: index).1 } override subscript(bounds: Range) -> ArraySlice { return ArraySlice(contents.subtree(withOffsets: bounds).lazy.map { $0.1 }) } override var value: Array { return Array(contents.lazy.map { $0.1 }) } override var count: Int { return contents.count } } ================================================ FILE: Sources/SetSortingByComparator.swift ================================================ // // SetSortingByComparator.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-07. // Copyright © 2015–2017 Károly Lőrentey. // extension ObservableSetType { public func sorted(by areInIncreasingOrder: @escaping (Element, Element) -> Bool) -> AnyObservableArray { let comparator = Comparator(areInIncreasingOrder) return self .sortedMap(by: { [unowned comparator] in ComparableWrapper($0, comparator) }) .map { [comparator] in _ = comparator; return $0.element } } public func sorted(by key: @escaping (Element) -> Key) -> AnyObservableArray { return self.sorted { a, b in key(a) < key(b) } } public func sorted(by comparator: Comparator) -> AnyObservableArray where Comparator.Value == (Element, Element) -> Bool { let reference: AnyObservableValue> = comparator.map { comparator in self.sorted(by: comparator).anyObservableArray } return reference.unpacked() } public func sorted(by key: ObservableKey) -> AnyObservableArray where ObservableKey.Value == (Element) -> Key { let reference: AnyObservableValue> = key.map { key in self.sorted(by: key).anyObservableArray } return reference.unpacked() } } private final class Comparator { let comparator: (Element, Element) -> Bool init(_ comparator: @escaping (Element, Element) -> Bool) { self.comparator = comparator } func compare(_ a: Element, _ b: Element) -> Bool { return comparator(a, b) } } private struct ComparableWrapper: Comparable { unowned(unsafe) let comparator: Comparator let element: Element init(_ element: Element, _ comparator: Comparator) { self.comparator = comparator self.element = element } static func ==(a: ComparableWrapper, b: ComparableWrapper) -> Bool { return !(a < b) && !(b < a) } static func <(a: ComparableWrapper, b: ComparableWrapper) -> Bool { return a.comparator.compare(a.element, b.element) } } ================================================ FILE: Sources/SetSortingByMappingToComparable.swift ================================================ // // SetSortingByMappingToComparable.swift // GlueKit // // Created by Károly Lőrentey on 2016-08-15. // Copyright © 2015–2017 Károly Lőrentey. // import BTree extension ObservableSetType { /// Given a transformation into a comparable type, return an observable array containing transformed /// versions of elements in this set, in increasing order. public func sortedMap(by transform: @escaping (Element) -> Result) -> AnyObservableArray { return SetSortingByMappingToComparable(parent: self, transform: transform).anyObservableArray } } extension ObservableSetType where Element: Comparable { /// Return an observable array containing the members of this set, in increasing order. public func sorted() -> AnyObservableArray { return self.sortedMap { $0 } } } private final class SetSortingByMappingToComparable: _BaseObservableArray { typealias Change = ArrayChange private struct SortingSink: UniqueOwnedSink { typealias Owner = SetSortingByMappingToComparable unowned(unsafe) let owner: Owner func receive(_ update: SetUpdate) { owner.applyParentUpdate(update) } } private let parent: Parent private let transform: (Parent.Element) -> Element private var contents: Map = [:] init(parent: Parent, transform: @escaping (Parent.Element) -> Element) { self.parent = parent self.transform = transform super.init() for element in parent.value { let transformed = transform(element) contents[transformed] = (contents[transformed] ?? 0) + 1 } parent.add(SortingSink(owner: self)) } deinit { parent.remove(SortingSink(owner: self)) } func applyParentUpdate(_ update: SetUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): var arrayChange = ArrayChange(initialCount: contents.count) for element in change.removed { let transformed = transform(element) guard let index = contents.index(forKey: transformed) else { fatalError("Removed element '\(transformed)' not found in sorted set") } let count = contents[index].1 if count == 1 { let offset = contents.offset(of: index) let old = contents.remove(at: index) if isConnected { arrayChange.add(.remove(old.key, at: offset)) } } else { contents[transformed] = count - 1 } } for element in change.inserted { let transformed = transform(element) if let count = contents[transformed] { precondition(count > 0) contents[transformed] = count + 1 } else { contents[transformed] = 1 if isConnected { let offset = contents.offset(of: transformed)! arrayChange.add(.insert(transformed, at: offset)) } } } if isConnected, !arrayChange.isEmpty { sendChange(arrayChange) } case .endTransaction: endTransaction() } } override var isBuffered: Bool { return false } override subscript(index: Int) -> Element { return contents.element(atOffset: index).0 } override subscript(bounds: Range) -> ArraySlice { return ArraySlice(contents.submap(withOffsets: bounds).lazy.map { $0.0 }) } override var value: Array { return Array(contents.lazy.map { $0.0 }) } override var count: Int { return contents.count } } ================================================ FILE: Sources/SetSortingByMappingToObservableComparable.swift ================================================ // // SetSortingByMappingToObservableComparable.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-07. // Copyright © 2015–2017 Károly Lőrentey. // import SipHash import BTree extension ObservableSetType where Element: AnyObject { /// Given a transformation into an observable of a comparable type, return an observable array /// containing transformed versions of elements in this set, in increasing order. public func sortedMap(by transform: @escaping (Element) -> Field) -> AnyObservableArray where Field.Value: Comparable { return SetSortingByMappingToObservableComparable(parent: self, transform: transform).anyObservableArray } } private class SetSortingByMappingToObservableComparable: _BaseObservableArray where Parent.Element: AnyObject, Field.Value: Comparable { typealias Element = Field.Value typealias Change = ArrayChange private struct ParentSink: UniqueOwnedSink { typealias Owner = SetSortingByMappingToObservableComparable unowned(unsafe) let owner: Owner func receive(_ update: SetUpdate) { owner.applyParentUpdate(update) } } private struct FieldSink: SinkType, SipHashable { typealias Owner = SetSortingByMappingToObservableComparable unowned(unsafe) let owner: Owner let element: Parent.Element func receive(_ update: ValueUpdate) { owner.applyFieldUpdate(update, from: element) } func appendHashes(to hasher: inout SipHasher) { hasher.append(ObjectIdentifier(owner)) hasher.append(element) } static func ==(left: FieldSink, right: FieldSink) -> Bool { return left.owner === right.owner && left.element == right.element } } private let parent: Parent private let transform: (Parent.Element) -> Field private var contents: Map = [:] private var fields: Dictionary = [:] init(parent: Parent, transform: @escaping (Parent.Element) -> Field) { self.parent = parent self.transform = transform super.init() for element in parent.value { _ = self._insert(newElement(element)) } parent.add(ParentSink(owner: self)) } deinit { parent.remove(ParentSink(owner: self)) for (sink, field) in fields { field.remove(sink) } } private func newElement(_ element: Parent.Element) -> Element { let field = transform(element) let sink = FieldSink(owner: self, element: element) let old = fields.updateValue(field, forKey: sink) field.add(sink) precondition(old == nil) return field.value } private func removeElement(_ element: Parent.Element) { let sink = FieldSink(owner: self, element: element) let field = fields.removeValue(forKey: sink)! field.remove(sink) } private func _insert(_ key: Element) -> Bool { if let count = contents[key] { contents[key] = count + 1 return false } contents[key] = 1 return true } private func insert(_ key: Element) -> ArrayModification? { return _insert(key) ? .insert(key, at: contents.offset(of: key)!) : nil } private func remove(_ key: Element) -> ArrayModification? { guard let count = self.contents[key] else { fatalError("Inconsistent change: element removed is not in sorted set") } if count > 1 { contents[key] = count - 1 return nil } let oldOffset = contents.offset(of: key)! contents.removeValue(forKey: key) return .remove(key, at: oldOffset) } func applyParentUpdate(_ update: SetUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): var arrayChange = ArrayChange(initialCount: contents.count) for element in change.removed { let key = transform(element).value removeElement(element) if let mod = self.remove(key) { arrayChange.add(mod) } } for element in change.inserted { let key = newElement(element) if let mod = self.insert(key) { arrayChange.add(mod) } } if !arrayChange.isEmpty { sendChange(arrayChange) } case .endTransaction: endTransaction() } } func applyFieldUpdate(_ update: ValueUpdate, from element: Parent.Element) { switch update { case .beginTransaction: beginTransaction() case .change(let change): var arrayChange = ArrayChange(initialCount: self.contents.count) if change.old == change.new { return } if let mod = remove(change.old) { arrayChange.add(mod) } if let mod = insert(change.new) { arrayChange.add(mod) } if !arrayChange.isEmpty { sendChange(arrayChange) } case .endTransaction: endTransaction() } } override var isBuffered: Bool { return false } override subscript(index: Int) -> Element { return contents.element(atOffset: index).0 } override subscript(bounds: Range) -> ArraySlice { return ArraySlice(contents.submap(withOffsets: bounds).lazy.map { $0.0 }) } override var value: Array { return Array(contents.lazy.map { $0.0 }) } override var count: Int { return contents.count } } ================================================ FILE: Sources/SetVariable.swift ================================================ // // SetVariable.swift // GlueKit // // Created by Károly Lőrentey on 2016-08-13. // Copyright © 2015–2017 Károly Lőrentey. // open class SetVariable: _BaseUpdatableSet, ExpressibleByArrayLiteral { public typealias Value = Set public typealias Change = SetChange fileprivate var _value: Value public override init() { _value = [] } public init(_ elements: [Element]) { _value = Set(elements) } public init(_ elements: Set) { _value = elements } public init(_ elements: S) where S.Iterator.Element == Element { _value = Set(elements) } public init(elements: Element...) { _value = Set(elements) } public required convenience init(arrayLiteral elements: Element...) { self.init(elements) } final public override var isBuffered: Bool { return true } public var isEmpty: Bool { return _value.isEmpty } final public override var count: Int { return _value.count } final public override var value: Value { get { return _value } set { beginTransaction() let v = _value _value = newValue sendChange(SetChange(removed: v, inserted: newValue)) endTransaction() } } final public override func contains(_ member: Element) -> Bool { return _value.contains(member) } final public override func isSubset(of other: Set) -> Bool { return _value.isSubset(of: other) } final public override func isSuperset(of other: Set) -> Bool { return _value.isSuperset(of: other) } override func rawApply(_ change: SetChange) { _value.apply(change) } final public override func remove(_ member: Element) { guard _value.contains(member) else { return } if isConnected { beginTransaction() _value.remove(member) sendChange(SetChange(removed: [member], inserted: [])) endTransaction() } else { _value.remove(member) } } final public override func insert(_ member: Element) { guard !_value.contains(member) else { return } if isConnected { beginTransaction() _value.insert(member) sendChange(SetChange(removed: [], inserted: [member])) endTransaction() } else { _value.insert(member) } } final public override func removeAll() { guard !isEmpty else { return } let value = self._value if isConnected { beginTransaction() _value.removeAll() sendChange(SetChange(removed: value, inserted: [])) endTransaction() } else { _value.removeAll() } } final public override func formUnion(_ other: Set) { if isConnected { beginTransaction() let difference = other.subtracting(_value) _value.formUnion(difference) sendChange(SetChange(removed: [], inserted: difference)) endTransaction() } else { _value.formUnion(other) } } final public override func formIntersection(_ other: Set) { if isConnected { beginTransaction() let difference = _value.subtracting(other) _value.subtract(difference) sendChange(SetChange(removed: difference, inserted: [])) endTransaction() } else { _value.formIntersection(other) } } final public override func formSymmetricDifference(_ other: Set) { if isConnected { beginTransaction() let intersection = _value.intersection(other) let additions = other.subtracting(self.value) _value.formSymmetricDifference(other) sendChange(SetChange(removed: intersection, inserted: additions)) endTransaction() } else { _value.formSymmetricDifference(other) } } final public override func subtract(_ other: Set) { if isConnected { beginTransaction() let intersection = _value.intersection(other) _value.subtract(other) sendChange(SetChange(removed: intersection, inserted: [])) endTransaction() } else { _value.subtract(other) } } } extension SetVariable { public func update(with member: Element) -> Element? { beginTransaction() let old = _value.update(with: member) if isConnected { if let old = old { sendChange(SetChange(removed: [old], inserted: [member])) } else { sendChange(SetChange(removed: [], inserted: [member])) } } endTransaction() return old } } ================================================ FILE: Sources/Signal.swift ================================================ // // Signal.swift // GlueKit // // Created by Károly Lőrentey on 2015-11-30. // Copyright © 2015–2017 Károly Lőrentey. // internal protocol SignalDelegate: class { func activate() func deactivate() } extension SignalDelegate { func activate() {} func deactivate() {} } private enum PendingItem { case sendValue(Value) case addSink(AnySink) } /// A Signal is both a source and a sink. Sending a value to a signal's sink forwards it to all sinks that are /// currently connected to the signal. The signal's sink is named "send", and it is available as a direct method. /// /// The Five Rules of Signal: /// /// 0. Signal only sends values to sinks that it receives via Signal.send(). All such values are forwarded to /// sinks connected at the time of the send (if any). /// 1. Sends are serialized. Signal defines a strict order between the values sent to it, forming a single /// sequence of values. /// 2. All sinks get the same values. Each sink receives a subsequence of the Signal's value sequence. No reordering, /// no skipping, no duplicates. /// 3. Connections are serialized with sends. Each sink's value subsequence starts with the first value that /// was (started to be) sent after it was connected (if any). /// 4. Disconnection is immediate. No new value is sent to a sink after its connection has finished disconnecting. /// /// Some implementation notes: /// /// The simplest nontrivial way to implement these rules is to make the Signal synchronous by serializing send() /// using a lock -- this is the way chosen by ReactiveCocoa and RxSwift. This makes reentrant send() calls deadlock, /// but that's probably a good thing. (You can emulate reentrant sends by scheduling an asyncronous send; however, /// that can visibly break value ordering.) /// /// As an experiment, this particular Signal implementation allows send() to be called from any thread at any /// time---including reentrant send()s from a sink that is currently being executed by it. To ensure the rule set /// above, reentrant and concurrent sends and connects are asynchronous. They are ordered in a queue and performed /// at the end of the active send that first entered the signal. This implementation never invokes sinks recursively /// inside another sink invocation. /// /// For reference, KVO's analogue to Signal in Foundation supports reentrancy, but its send() is synchronous. There /// is no way to satisfy the above rules in a system like that. KVO's designers chose to resolve this by always calling /// observers with the latest value of the observed key path. That's a nice pragmatic solution in the face of /// reentrancy, but it only makes sense when you have the concept of a current value, which Signal doesn't. /// (Although Variable does.) /// public class Signal: _AbstractSource { internal weak var delegate: SignalDelegate? private let lock = Lock() private var sending = false private var sinks: Set> = [] private var pendingItems: [PendingItem] = [] public override init() { self.delegate = nil super.init() } internal init(delegate: SignalDelegate) { self.delegate = delegate super.init() } deinit { precondition(sinks.count == 0 && pendingItems.count == 0) } /// Atomically return the pending value that needs to be sent next, or nil. /// If there are no more values, exit the sending state. private func _nextValueToSend(enterSending: Bool) -> Value? { return lock.withLock { if enterSending { if self.sending { return nil } self.sending = true } else { assert(self.sending) } while case .some(let item) = self.pendingItems.first { self.pendingItems.removeFirst() switch item { case .sendValue(let value): if !self.sinks.isEmpty { // Skip value if there are no sinks. // Send the next value to all ripe sinks. return value } case .addSink(let sink): let (inserted, _) = self.sinks.insert(sink) precondition(inserted, "Sink is already subscribed to this signal") } } // There are no more items to process. self.sending = false return nil } } /// Synchronously send a value to all connected sinks. private func _sendValueNow(_ value: Value) { assert(lock.withLock { sending }) // Note that sinks are allowed to freely add or remove connections. // This loop is constructed to support this correctly: // - New sinks added while we are sending a value will not fire. // - Sinks removed during the iteration will not fire. for sink in lock.withLock({ self.sinks }) { if lock.withLock({ self.sinks.contains(sink) }) { sink.receive(value) } } } /// Send a value to all sinks currently connected to this Signal. The sinks are executed in undefined order. /// /// The sinks are normally executed synchronously. However, when two or more threads are sending values at the /// same time, or when a connected sink sends a value back to this same signal, then only the send() arriving /// first is synchronous; the rest are performed asynchronously on the thread of the first send(), before the /// first send() returns. /// /// You may safely call this method from any thread, provided that the sinks are OK with running there. public func send(_ value: Value) { let path: Bool? = lock.withLock { if sending { self.pendingItems.append(.sendValue(value)) return nil // Value has been scheduled; somebody else will send it. } sending = true if self.pendingItems.isEmpty { return true } self.pendingItems.append(.sendValue(value)) return false } if let fast = path { if fast { // Fast track: We can send the value immediately. _sendValueNow(value) } // Send remaining pending items in order. while let v = _nextValueToSend(enterSending: false) { _sendValueNow(v) } } } /// Append value to the queue of pending values. The value will be sent by a send() or sendNow() invocation. /// (If sendNow() is already running (recursively up the call stack, or on another thread), then the value will be /// sent by that invocation. If not, the first upcoming send will send the value.) /// /// Calls to sendLater() should always be followed by at least one call to sendNow(). /// /// This construct is useful to control the ordering of notifications about changes to a value in the face of /// concurrent modifications, without calling send() inside a lock. For example, here is a thread-safe, reentrant /// counter that guarantees to send increasing counts, without holding a lock during sending: /// /// ``` /// public struct Counter { /// private let mutex = Mutex() /// private var count: Int = 0 /// private let signal = Signal() /// /// public var source: Source { return signal.source } /// /// public mutating func increment() { /// let value: Int = mutex.withLock { /// let v = ++count /// signal.sendLater(v) /// return v /// } /// signal.sendNow() /// return value /// } /// } /// ``` internal func sendLater(_ value: Value) { lock.withLock { self.pendingItems.append(.sendValue(value)) } } /// Send all pending values immediately, or do nothing if the signal is already sending values elsewhere. /// (On another thread, or if this is a recursive call of sendNow on the current thread.) internal func sendNow() { if let value = _nextValueToSend(enterSending: true) { _sendValueNow(value) while let value = _nextValueToSend(enterSending: false) { _sendValueNow(value) } } } public override func add(_ sink: Sink) where Sink.Value == Value { let sink = sink.anySink let first: Bool = lock.withLock { let first = self.sinks.isEmpty if self.pendingItems.isEmpty { let (inserted, _) = self.sinks.insert(sink) precondition(inserted, "Sink is already subscribed to this signal") } else { // Values that are currently pending should not be sent to this sink, but any future values should be. self.pendingItems.append(.addSink(sink)) } return first } if first { delegate?.activate() } } @discardableResult public override func remove(_ sink: Sink) -> Sink where Sink.Value == Value { let sink = sink.anySink let (last, old): (Bool, AnySink) = lock.withLock { var old = self.sinks.remove(sink) if old == nil { for i in 0 ..< pendingItems.count { if case .addSink(let s) = pendingItems[i], s == sink { old = s pendingItems.remove(at: i) break } } } precondition(old != nil, "Sink is not subscribed to this signal") return (self.sinks.isEmpty, old!) } if last { delegate?.deactivate() } return old.opened()! } public var isConnected: Bool { return lock.withLock { !self.sinks.isEmpty } } } extension Signal { public var asSink: AnySink { return SignalSink(self).anySink } } extension Signal where Value == Void { public func send() { self.send(()) } } private struct SignalSink: SinkType { private let signal: Signal init(_ signal: Signal) { self.signal = signal } func receive(_ value: Value) { signal.send(value) } var hashValue: Int { return ObjectIdentifier(signal).hashValue } static func ==(left: SignalSink, right: SignalSink) -> Bool { return left.signal === right.signal } } ================================================ FILE: Sources/SimpleSources.swift ================================================ // // SimpleSources.swift // GlueKit // // Created by Károly Lőrentey on 2015-11-30. // Copyright © 2015–2017 Károly Lőrentey. // extension SourceType { /// Returns a source that never fires. public static func empty() -> AnySource { return NeverSource().anySource } /// Returns a source that never fires. public static func never() -> AnySource { return NeverSource().anySource } } class NeverSource: _AbstractSource { override func add(_ sink: Sink) where Sink.Value == Value { // Do nothing. } @discardableResult override func remove(_ sink: Sink) -> Sink where Sink.Value == Value { // Do nothing. return sink } } extension SourceType { /// Returns a source that fires exactly once with the given value, then never again. public static func just(_ value: Value) -> AnySource { return JustSource(value).anySource } } class JustSource: _AbstractSource { private var value: Value init(_ value: Value) { self.value = value super.init() } override func add(_ sink: Sink) where Sink.Value == Value { sink.receive(value) } @discardableResult override func remove(_ sink: Sink) -> Sink where Sink.Value == Value { return sink } } ================================================ FILE: Sources/Sink.swift ================================================ // // Sink.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-22. // Copyright © 2015–2017 Károly Lőrentey. // public protocol SinkType: Hashable { associatedtype Value func receive(_ value: Value) var anySink: AnySink { get } } extension SinkType { public var anySink: AnySink { return AnySink(SinkBox(self)) } } extension SinkType where Self: AnyObject { public func unowned() -> AnySink { return UnownedSink(self).anySink } public var hashValue: Int { return ObjectIdentifier(self).hashValue } public static func ==(a: Self, b: Self) -> Bool { return a === b } } public struct AnySink: SinkType { // TODO: Replace this with a generalized protocol existential when Swift starts supporting those. // (We always need to allocate a box for the sink, while an existential may have magical inline storage for // tiny sinks -- say, less than three words.) private let box: _AbstractSink fileprivate init(_ box: _AbstractSink) { self.box = box } public func receive(_ value: Value) { box.receive(value) } public var anySink: AnySink { return self } public var hashValue: Int { return box.hashValue } public static func ==(left: AnySink, right: AnySink) -> Bool { return left.box == right.box } public func opened(as type: Sink.Type = Sink.self) -> Sink? where Sink.Value == Value { if let sink = self as? Sink { return sink } if let sink = box as? Sink { return sink } if let box = self.box as? SinkBox { return box.contents } return nil } } fileprivate class _AbstractSink: SinkType { // TODO: Eliminate this when Swift starts supporting generalized protocol existentials. func receive(_ value: Value) { abstract() } var hashValue: Int { abstract() } func isEqual(to other: _AbstractSink) -> Bool { abstract() } public static func ==(left: _AbstractSink, right: _AbstractSink) -> Bool { return left.isEqual(to: right) } public final var anySink: AnySink { return AnySink(self) } } fileprivate class SinkBox: _AbstractSink { // TODO: Eliminate this when Swift starts supporting generalized protocol existentials. typealias Value = Wrapped.Value fileprivate let contents: Wrapped init(_ contents: Wrapped) { self.contents = contents } override func receive(_ value: Value) { contents.receive(value) } override var hashValue: Int { return contents.hashValue } override func isEqual(to other: _AbstractSink) -> Bool { guard let other = other as? SinkBox else { return false } return self.contents == other.contents } } fileprivate class UnownedSink: _AbstractSink { typealias Value = Wrapped.Value unowned let wrapped: Wrapped init(_ wrapped: Wrapped) { self.wrapped = wrapped } override func receive(_ value: Value) { wrapped.receive(value) } override var hashValue: Int { return wrapped.hashValue } override func isEqual(to other: _AbstractSink) -> Bool { guard let other = other as? UnownedSink else { return false } return self.wrapped == other.wrapped } } ================================================ FILE: Sources/Source.swift ================================================ // // Source.swift // GlueKit // // Created by Károly Lőrentey on 2015-11-30. // Copyright © 2015–2017 Károly Lőrentey. // /// A Source is an entity that is able to produce values to other entities (called Sinks) that are connected to it. /// A source can be an observable value (see Variable), a KVO-compatible key path on an object /// (see NSObject.sourceForKeyPath), a notification (see NSNotificationCenter.sourceForNotification), /// a timer (see TimerSource), etc. etc. /// /// Sources implement the `SourceType` protocol. It only has a single method, `subscribe`; it can be used to subscribe /// new sinks to values produced by this source. /// /// `SourceType` is a protocol with an associated value, which can be sometimes inconvenient to work with. /// GlueKit provides the struct `Source` to represent a type-erased source. /// /// A source is intended to be equivalent to a read-only propery. Therefore, while a source typically has a mechanism /// for sending values, this is intentionally outside the scope of `SourceType`. (But see `Signal`). /// /// We represent a source by a struct holding the subscription closure; this allows extensions on it, which is convenient. /// GlueKit provides built-in extension methods for transforming sources to other kinds of sources. /// public protocol SourceType { /// The type of values produced by this source. associatedtype Value /// Subscribe `sink` to this source, i.e., retain the sink and start calling its `receive` function /// whenever this source produces a value. /// The subscription remains active until `remove` is called with an identical sink. /// /// - SeeAlso: `subscribe`, `remove` func add(_ sink: Sink) where Sink.Value == Value /// Remove `sink`'s subscription to this source, i.e., stop calling the sink's `receive` function and release it. /// The subscription remains active until `remove` is called with an identical sink. /// /// - Returns: The sink that was previously added to the sink. /// This may be distinguishable by the input parameter by identity comparison or some other means. /// - SeeAlso: `subscribe`, `add` @discardableResult func remove(_ sink: Sink) -> Sink where Sink.Value == Value /// A type-erased representation of this source. var anySource: AnySource { get } } extension SourceType { public var anySource: AnySource { return AnySource(box: SourceBox(self)) } } /// A Source is an entity that is able to produce values to other entities (called Sinks) that are connected to it. /// A source can be an observable value (see Variable), a KVO-compatible key path on an object /// (see NSObject.sourceForKeyPath), a notification (see NSNotificationCenter.sourceForNotification), /// a timer (see TimerSource), etc. etc. /// /// Sources implement the `SourceType` protocol. It only has a single method, `subscribe`; it can be used to subscribe /// new sinks to values produced by this source. /// /// `SourceType` is a protocol with an associated value, which is sometimes inconvenient to work with. GlueKit /// provides the struct `Source` to represent a type-erased source. /// /// A source is intended to be equivalent to a read-only propery. Therefore, while a source typically has a mechanism /// for sending values, this is intentionally outside the scope of `SourceType`. (But see `Signal`). /// /// We represent a source by a struct holding the subscription closure; this allows extensions on it, which is convenient. /// GlueKit provides built-in extension methods for transforming sources to other kinds of sources. /// public struct AnySource: SourceType { private let box: _AbstractSource internal init(box: _AbstractSource) { self.box = box } public func add(_ sink: Sink) where Sink.Value == Value { box.add(sink) } @discardableResult public func remove(_ sink: Sink) -> Sink where Sink.Value == Value { return box.remove(sink) } public var anySource: AnySource { return self } } open class _AbstractSource: SourceType { open func add(_ sink: Sink) where Sink.Value == Value { abstract() } @discardableResult open func remove(_ sink: Sink) -> Sink where Sink.Value == Value { abstract() } public final var anySource: AnySource { return AnySource(box: self) } } open class SignalerSource: _AbstractSource, SignalDelegate { internal lazy var signal: Signal = .init(delegate: self) public final override func add(_ sink: Sink) where Sink.Value == Value { self.signal.add(sink) } @discardableResult public final override func remove(_ sink: Sink) -> Sink where Sink.Value == Value { return self.signal.remove(sink) } func activate() {} func deactivate() {} } internal class SourceBox: _AbstractSource { typealias Value = Base.Value let base: Base init(_ base: Base) { self.base = base } override func add(_ sink: Sink) where Sink.Value == Value { base.add(sink) } @discardableResult override func remove(_ sink: Sink) -> Sink where Sink.Value == Value { return base.remove(sink) } } ================================================ FILE: Sources/TimerSource.swift ================================================ // // TimerSource.swift // GlueKit // // Created by Károly Lőrentey on 2015-11-30. // Copyright © 2015–2017 Károly Lőrentey. // import Foundation // Some convenient extensions for DispatchTime, making it understand Foundation types extension DispatchWallTime { internal init(_ date: Date) { let secsSinceEpoch = date.timeIntervalSince1970 let spec = timespec( tv_sec: __darwin_time_t(secsSinceEpoch), tv_nsec: Int((secsSinceEpoch - floor(secsSinceEpoch)) * Double(NSEC_PER_SEC)) ) self.init(timespec: spec) } } extension DispatchQueue { func async(afterDelay interval: TimeInterval, execute block: @escaping @convention(block) () -> Void) { self.asyncAfter(deadline: DispatchTime.now() + interval, execute: block) } func async(after date: Date, execute block: @escaping @convention(block) () -> Void) { self.asyncAfter(wallDeadline: DispatchWallTime(date), execute: block) } } private class AtomicToken { // TODO: This should use atomics, which are currently (Xcode 8 beta 5) unavailable in Swift private let lock = Lock() private var token: Int = 0 @discardableResult func increment() -> Int { return lock.withLock { token += 1 return token } } func equals(_ value: Int) -> Bool { return lock.withLock { return token == value } } } public typealias TimerSource = _TimerSource /// A Source that is firing at customizable intervals. The time of the each firing is determined by a user-supplied closure. /// /// The timer interval should be relatively large (at least multiple seconds); this is not supposed to be a realtime timer. /// /// Note that this source will only schedule an actual timer while there are sinks connected to it. public final class _TimerSource: SignalerSource { private let queue: DispatchQueue private let next: () -> Date? private var token = AtomicToken() override func activate() { start() } override func deactivate() { stop() } /// Set up a new TimerSource that is scheduled on a given queue at the times determined by the supplied block. /// @param queue The queue on which to schedule the timer. /// The signal will fire on this queue. If unspecified, the main queue is used. /// @param next A closure that returns the next date the signal is supposed to fire, /// or nil if the timer should be paused indefinitely. The closure is executed on the queue in the first parameter. /// /// Note that the `next` closure will not be called immediately; the source waits for the first connection /// before establishing a timer. public init(queue: DispatchQueue = DispatchQueue.main, next: @escaping () -> Date?) { self.queue = queue self.next = next } /// Stop the timer. The timer will not fire again until start() is called. public func stop() { // Cancel the existing scheduling chain. self.token.increment() } /// Start the timer, or if it is already running, recalculate the next firing date and reschedule the timer immediately. /// Call this method when the dependencies of the `next` closure have changed since the last firing, and you want to the timer to apply the changes before it fires next. public func start() { // Start a new scheduling chain. let frozenToken = token.increment() queue.async { self.scheduleNext(frozenToken) } } private func scheduleNext(_ frozenToken: Int) { guard token.equals(frozenToken) else { return } if let nextDate = next() { queue.async(after: nextDate) { [weak self] in self?.fireWithToken(frozenToken) } } } private func fireWithToken(_ frozenToken: Int) { guard token.equals(frozenToken) else { return } self.signal.send() scheduleNext(frozenToken) } } /// Encapsulates information on a periodic timer and associated logic to determine firing dates. /// Tries to prevent timer drift by using walltime-based, absolute firing dates instead of relative ones. private struct PeriodicTimerData { let start: Date let interval: TimeInterval var currentTick: Int { let now = Date() let elapsed = max(0, now.timeIntervalSince(start)) let tick = floor(elapsed / interval) return Int(tick) } var dateOfNextTick: Date { return start.addingTimeInterval(TimeInterval(currentTick + 1) * interval) } } public extension _TimerSource { /// Creates a TimerSource that triggers periodically with a specific time interval. /// /// This source makes an effort to prevent timer drift by scheduling ticks at predetermined absolute time points, /// but it only guarantees that ticks happen sometime after their scheduled trigger time, with no upper bound to the delay. /// /// If the system is busy (or sleeping) some ticks may be skipped. /// /// @param queue: The queue on which to schedule the timer. The signal will fire on this queue. If unspecified, the main queue is used. /// @param start: The time at which the source should fire first, or nil to begin firing `interval` seconds from now. /// @param interval: The minimum time period between the beginnings of subsequent firings. public convenience init(queue: DispatchQueue = DispatchQueue.main, start: Date? = nil, interval: TimeInterval) { assert(interval > 0) let data = PeriodicTimerData(start: start ?? Date().addingTimeInterval(interval), interval: interval) self.init(queue: queue, next: { [data] in data.dateOfNextTick }) } } ================================================ FILE: Sources/TransactionalThing.swift ================================================ // // TransactionState.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-22. // Copyright © 2015–2017 Károly Lőrentey. // internal class TransactionalSignal: Signal> { typealias Value = Update var isInTransaction: Bool = false public override func add(_ sink: Sink) where Sink.Value == Value { if self.isInTransaction { // Make sure the new subscriber knows we're in the middle of a transaction. sink.receive(.beginTransaction) } super.add(sink) } @discardableResult public override func remove(_ sink: Sink) -> Sink where Sink.Value == Value { let old = super.remove(sink) if self.isInTransaction { // Wave goodbye by sending a virtual endTransaction that makes state management easier. old.receive(.endTransaction) } return old } } protocol TransactionalThing: class, SignalDelegate { associatedtype Change: ChangeType var _signal: TransactionalSignal? { get set } var _transactionCount: Int { get set } } extension TransactionalThing { var signal: TransactionalSignal { if let signal = _signal { return signal } let signal = TransactionalSignal() signal.isInTransaction = _transactionCount > 0 signal.delegate = self _signal = signal return signal } func beginTransaction() { _transactionCount += 1 if _transactionCount == 1, let signal = _signal { signal.isInTransaction = true signal.send(.beginTransaction) } } func sendChange(_ change: Change) { precondition(_transactionCount > 0) _signal?.send(.change(change)) } func sendIfConnected(_ change: @autoclosure () -> Change) { if isConnected { sendChange(change()) } } func endTransaction() { precondition(_transactionCount > 0) _transactionCount -= 1 if _transactionCount == 0, let signal = _signal { signal.isInTransaction = false signal.send(.endTransaction) } } public func send(_ update: Update) { switch update { case .beginTransaction: beginTransaction() case .change(let change): sendChange(change) case .endTransaction: endTransaction() } } var isInTransaction: Bool { return _transactionCount > 0 } var isConnected: Bool { return _signal?.isConnected ?? false } var isActive: Bool { return isInTransaction || isConnected } var isInOuterMostTransaction: Bool { return _transactionCount == 1 } // Used by KVO } public class TransactionalSource: _AbstractSource>, TransactionalThing { internal var _signal: TransactionalSignal? = nil internal var _transactionCount = 0 func activate() { } func deactivate() { } public override func add(_ sink: Sink) where Sink.Value == Value { signal.add(sink) } @discardableResult public override func remove(_ sink: Sink) -> Sink where Sink.Value == Value { return signal.remove(sink) } } ================================================ FILE: Sources/TransformedSink.swift ================================================ // // TransformedSink.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-29. // Copyright © 2015–2017 Károly Lőrentey. // import SipHash public protocol SinkTransform: Hashable { associatedtype Input associatedtype Output func apply(_ input: Input, _ sink: Sink) where Sink.Value == Output } extension SinkTransform where Self: AnyObject { public var hashValue: Int { return ObjectIdentifier(self).hashValue } public static func ==(a: Self, b: Self) -> Bool { return a === b } } extension SinkType { func transform(_ transform: Transform) -> TransformedSink { return TransformedSink(sink: self, transform: transform) } } public struct TransformedSink: SinkType, SipHashable where Transform.Output == Sink.Value { public let sink: Sink public let transform: Transform public func receive(_ value: Transform.Input) { transform.apply(value, sink) } public func appendHashes(to hasher: inout SipHasher) { hasher.append(sink) hasher.append(transform) } public static func ==(left: TransformedSink, right: TransformedSink) -> Bool { return left.sink == right.sink && left.transform == right.transform } } class SinkTransformFromClosure: SinkTransform { typealias Input = I typealias Output = O let transform: (Input, (Output) -> Void) -> Void init(_ transform: @escaping (Input, (Output) -> Void) -> Void) { self.transform = transform } func apply(_ input: Input, _ sink: Sink) where Sink.Value == Output { transform(input, sink.receive) } } class SinkTransformToConstant: SinkTransform { typealias Input = I typealias Output = O let constant: O init(_ constant: O) { self.constant = constant } func apply(_ input: Input, _ sink: Sink) where Sink.Value == Output { sink.receive(constant) } } class SinkTransformFromMapping: SinkTransform { typealias Input = I typealias Output = O let mapping: (Input) -> Output init(_ mapping: @escaping (Input) -> Output) { self.mapping = mapping } func apply(_ input: Input, _ sink: Sink) where Sink.Value == Output { sink.receive(mapping(input)) } } class SinkTransformFromOptionalMapping: SinkTransform { typealias Input = I typealias Output = O let mapping: (Input) -> Output? init(_ mapping: @escaping (Input) -> Output?) { self.mapping = mapping } func apply(_ input: Input, _ sink: Sink) where Sink.Value == Output { if let output = mapping(input) { sink.receive(output) } } } class SinkTransformFromFilter: SinkTransform { typealias Input = I typealias Output = I let filter: (Input) -> Bool init(_ filter: @escaping (Input) -> Bool) { self.filter = filter } func apply(_ input: Input, _ sink: Sink) where Sink.Value == Output { if filter(input) { sink.receive(input) } } } class SinkTransformFromSequence: SinkTransform { typealias Output = S.Iterator.Element let transform: (Input) -> S init(_ transform: @escaping (Input) -> S) { self.transform = transform } func apply(_ input: Input, _ sink: Sink) where Sink.Value == Output { for output in transform(input) { sink.receive(output) } } } ================================================ FILE: Sources/TransformedSource.swift ================================================ // // TransformedSource.swift // GlueKit // // Created by Károly Lőrentey on 2015-11-30. // Copyright © 2015–2017 Károly Lőrentey. // final class TransformedSource: _AbstractSource where Transform.Input == Input.Value { let input: Input let transform: Transform init(input: Input, transform: Transform) { self.input = input self.transform = transform } final override func add(_ sink: Sink) where Sink.Value == Transform.Output { self.input.add(sink.transform(transform)) } @discardableResult final override func remove(_ sink: Sink) -> Sink where Sink.Value == Transform.Output { return self.input.remove(sink.transform(transform)).sink } } extension SourceType { /// Returns a source that, applies `transform` on each value produced by `self` and each subscriber sink. /// /// `transform` should be a pure function, i.e., one with no side effects or hidden parameters. /// For each value that is received from `self`, it is called as many times as there are subscribers. public func transform(_ type: Result.Type = Result.self, _ transform: @escaping (Value, (Result) -> Void) -> Void) -> AnySource { return TransformedSource(input: self, transform: SinkTransformFromClosure(transform)).anySource } public func map(_ transform: @escaping (Value) -> Output) -> AnySource { return TransformedSource(input: self, transform: SinkTransformFromMapping(transform)).anySource } public func flatMap(_ transform: @escaping (Value) -> Output?) -> AnySource { return TransformedSource(input: self, transform: SinkTransformFromOptionalMapping(transform)).anySource } public func filter(_ predicate: @escaping (Value) -> Bool) -> AnySource { return TransformedSource(input: self, transform: SinkTransformFromFilter(predicate)).anySource } public func flatMap(_ transform: @escaping (Value) -> S) -> AnySource { return TransformedSource(input: self, transform: SinkTransformFromSequence(transform)).anySource } public func mapToVoid() -> AnySource { return TransformedSource(input: self, transform: SinkTransformToConstant(())).anySource } public func mapToConstant(_ value: C) -> AnySource { return TransformedSource(input: self, transform: SinkTransformToConstant(value)).anySource } } ================================================ FILE: Sources/TwoWayBinding.swift ================================================ // // TwoWayBinding.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-24. // Copyright © 2015–2017 Károly Lőrentey. // extension UpdatableValueType { /// Create a two-way binding from self to a target updatable. The target is updated to the current value of self. /// All future updates will be synchronized between the two variables until the returned connection is disconnected. /// To prevent infinite cycles, you must provide an equality test that returns true if two values are to be /// considered equivalent. public func bind(to target: Target, by areEquivalent: @escaping (Value, Value) -> Bool) -> Connection where Target.Value == Value { return BindConnection(source: self, target: target, by: areEquivalent) } } private enum BindOrigin { case source case target } private struct BindSink: OwnedSink where Source.Value == Target.Value { typealias Owner = BindConnection unowned let owner: Owner let origin: BindOrigin var identifier: BindOrigin { return origin } init(owner: Owner, origin: BindOrigin) { self.owner = owner self.origin = origin } private func send(_ update: Update>) { switch origin { case .source: owner.target!.apply(update) case .target: owner.source!.apply(update) } } private var destinationValue: Source.Value { switch origin { case .source: return owner.target!.value case .target: return owner.source!.value } } func receive(_ update: Update>) { switch update { case .beginTransaction: switch owner.transactionOrigin { case nil: owner.transactionOrigin = origin send(update) case .some(origin): preconditionFailure("Duplicate transaction") case .some(_): // Ignore break } case .change(let change): if !owner.areEquivalent!(change.new, destinationValue) { send(update) } case .endTransaction: switch owner.transactionOrigin { case nil: preconditionFailure("End received for nonexistent transaction") case .some(origin): send(update) owner.transactionOrigin = nil case .some(_): // Ignore break } } } } private final class BindConnection: Connection where Source.Value == Target.Value { typealias Value = Source.Value var areEquivalent: ((Value, Value) -> Bool)? var source: Source? var target: Target? var transactionOrigin: BindOrigin? = nil init(source: Source, target: Target, by areEquivalent: @escaping (Value, Value) -> Bool) { self.areEquivalent = areEquivalent self.source = source self.target = target super.init() source.add(BindSink(owner: self, origin: .source)) precondition(transactionOrigin == nil, "Binding during an active transaction is not supported") target.add(BindSink(owner: self, origin: .target)) precondition(transactionOrigin == nil, "Binding during an active transaction is not supported") if !areEquivalent(source.value, target.value) { target.value = source.value } } deinit { disconnect() } override func disconnect() { precondition(transactionOrigin == nil, "Unbinding during an active transaction is not supported") if let source = self.source { source.remove(BindSink(owner: self, origin: .source)) } if let source = self.target { source.remove(BindSink(owner: self, origin: .target)) } self.areEquivalent = nil self.source = nil self.target = nil } } extension UpdatableValueType where Value: Equatable { /// Create a two-way binding from self to a target variable. The target is updated to the current value of self. /// All future updates will be synchronized between the two variables until the returned connection is disconnected. /// To prevent infinite cycles, the variables aren't synched when a bound variable is set to a value that is equal /// to the value of its counterpart. public func bind(to target: Target) -> Connection where Target.Value == Value { return self.bind(to: target, by: ==) } } extension Connector { @discardableResult public func bind(_ source: Source, to target: Target, by areEquivalent: @escaping (Source.Value, Source.Value) -> Bool) -> Connection where Source.Value == Target.Value { return source.bind(to: target, by: areEquivalent).putInto(self) } @discardableResult public func bind(_ source: Source, to target: Target) -> Connection where Source.Value == Value, Target.Value == Value { return self.bind(source, to: target, by: ==) } } ================================================ FILE: Sources/Type Helpers.swift ================================================ // // Type Helpers.swift // GlueKit // // Created by Károly Lőrentey on 2016-03-10. // Copyright © 2015–2017 Károly Lőrentey. // import Foundation #if os(macOS) import AppKit #elseif os(iOS) || os(tvOS) import UIKit // For NSValue.CGPointValue and similar conversions #elseif os(watchOS) import WatchKit #endif private func toString(_ value: Any?) -> String { return value as! String } private func toBool(_ value: Any?) -> Bool { let v = value as! NSNumber return v.boolValue } private func toInt(_ value: Any?) -> Int { let v = value as! NSNumber return v.intValue } private func toFloat(_ value: Any?) -> Float { let v = value as! NSNumber return v.floatValue } private func toDouble(_ value: Any?) -> Double { let v = value as! NSNumber return v.doubleValue } private func toCGFloat(_ value: Any?) -> CGFloat { let v = value as! NSNumber return CGFloat(v.doubleValue) } private func toCGPoint(_ value: Any?) -> CGPoint { let v = value as! NSValue #if os(macOS) return v.pointValue #else return v.cgPointValue #endif } private func toCGSize(_ value: Any?) -> CGSize { let v = value as! NSValue #if os(macOS) return v.sizeValue #else return v.cgSizeValue #endif } private func toCGRect(_ value: Any?) -> CGRect { let v = value as! NSValue #if os(macOS) return v.rectValue #else return v.cgRectValue #endif } private let encodedCGAffineTransform = (CGFloat.NativeType.self == Double.self ? "{CGAffineTransform=dddddd}" : "{CGAffineTransform=ffffff}") private func toCGAffineTransform(_ value: Any?) -> CGAffineTransform { let v = value as! NSValue #if os(macOS) precondition(String(cString: v.objCType) == encodedCGAffineTransform) var transform = CGAffineTransform.identity v.getValue(&transform) return transform #else return v.cgAffineTransformValue #endif } public extension SourceType where Value == Any? { /// Casts all values to Type using an unsafe cast. Signals a fatal error if a value isn't a Type. func forceCasted(to type: T.Type = T.self) -> AnySource { return self.map { $0 as! T } } func casted(to type: T.Type = T.self) -> AnySource { return self.map { $0 as? T } } func casted(to type: T.Type = T.self, defaultValue: T) -> AnySource { return self.map { ($0 as? T) ?? defaultValue } } /// Casts all values to String via NSString. Signals a fatal error if a value isn't an NSString. var asString: AnySource { return map(toString) } /// Converts all values to Bool using NSNumber.boolValue. Signals a fatal error if a value isn't an NSNumber. var asBool: AnySource { return map(toBool) } /// Converts all values to Int using NSNumber.integerValue. Signals a fatal error if a value isn't an NSNumber. var asInt: AnySource { return map(toInt) } /// Converts all values to Float using NSNumber.floatValue. Signals a fatal error if a value isn't an NSNumber. var asFloat: AnySource { return map(toFloat) } /// Converts all values to Double using NSNumber.doubleValue. Signals a fatal error if a value isn't an NSNumber. var asDouble: AnySource { return map(toDouble) } /// Converts all values to CGFloat using NSNumber.doubleValue. Signals a fatal error if a value isn't an NSNumber. var asCGFloat: AnySource { return map(toCGFloat) } /// Converts all values to CGPoint using NSValue.CGPointValue. Signals a fatal error if a value isn't an NSValue. var asCGPoint: AnySource { return map(toCGPoint) } /// Converts all values to CGSize using NSValue.CGSizeValue. Signals a fatal error if a value isn't an NSValue. var asCGSize: AnySource { return map(toCGSize) } /// Converts all values to CGRect using NSValue.CGRectValue. Signals a fatal error if a value isn't an NSValue. var asCGRect: AnySource { return map(toCGRect) } /// Converts all values to CGAffineTransformValue using NSValue.CGAffineTransformValue. Signals a fatal error if a value isn't an NSValue. var asCGAffineTransform: AnySource { return map(toCGAffineTransform) } } public extension ObservableValueType where Value == Any? { /// Casts all values to Type using a forced cast. Traps if a value can't be casted to the specified type. func forceCasted(to type: T.Type = T.self) -> AnyObservableValue { return self.map { $0 as! T } } func casted(to type: T.Type = T.self) -> AnyObservableValue { return self.map { $0 as? T } } func casted(to type: T.Type = T.self, defaultValue: T) -> AnyObservableValue { return self.map { ($0 as? T) ?? defaultValue } } /// Casts all values to String via NSString. Signals a fatal error if a value isn't an NSString. var asString: AnyObservableValue { return self.map(toString) } /// Converts all values to Bool using NSNumber.integerValue. Signals a fatal error if a value isn't an NSNumber. var asBool: AnyObservableValue { return self.map(toBool) } /// Converts all values to Int using NSNumber.integerValue. Signals a fatal error if a value isn't an NSNumber. var asInt: AnyObservableValue { return self.map(toInt) } /// Converts all values to Float using NSNumber.floatValue. Signals a fatal error if a value isn't an NSNumber. var asFloat: AnyObservableValue { return self.map(toFloat) } /// Converts all values to Double using NSNumber.doubleValue. Signals a fatal error if a value isn't an NSNumber. var asDouble: AnyObservableValue { return self.map(toDouble) } /// Converts all values to CGFloat using NSNumber.doubleValue. Signals a fatal error if a value isn't an NSNumber. var asCGFloat: AnyObservableValue { return self.map(toCGFloat) } /// Converts all values to CGPoint using NSValue.CGPointValue. Signals a fatal error if a value isn't an NSValue. var asCGPoint: AnyObservableValue { return self.map(toCGPoint) } /// Converts all values to CGSize using NSValue.CGSizeValue. Signals a fatal error if a value isn't an NSValue. var asCGSize: AnyObservableValue { return self.map(toCGSize) } /// Converts all values to CGRect using NSValue.CGRectValue. Signals a fatal error if a value isn't an NSValue. var asCGRect: AnyObservableValue { return self.map(toCGRect) } /// Converts all values to CGAffineTransformValue using NSValue.CGAffineTransformValue. Signals a fatal error if a value isn't an NSValue. var asCGAffineTransform: AnyObservableValue { return self.map(toCGAffineTransform) } } public extension UpdatableValueType where Value == Any? { /// Casts all values to Type using a forced cast. Traps if a value can't be casted to the specified type. func forceCasted(to type: T.Type = T.self) -> AnyUpdatableValue { return self.map({ $0 as! T }, inverse: { $0 as Any? }) } func casted(to type: T.Type = T.self) -> AnyUpdatableValue { return self.map({ $0 as? T }, inverse: { $0 as Any? }) } func casted(to type: T.Type = T.self, defaultValue: T) -> AnyUpdatableValue { return self.map({ ($0 as? T) ?? defaultValue }, inverse: { $0 as Any? }) } /// Casts all values to String via NSString. Signals a fatal error if a value isn't an NSString. var asString: AnyUpdatableValue { return self.map(toString, inverse: { $0 }) } /// Converts all values to Bool using NSNumber.integerValue. Signals a fatal error if a value isn't an NSNumber. var asBool: AnyUpdatableValue { return self.map(toBool, inverse: { NSNumber(value: $0) }) } /// Converts all values to Int using NSNumber.integerValue. Signals a fatal error if a value isn't an NSNumber. var asInt: AnyUpdatableValue { return self.map(toInt, inverse: { NSNumber(value: $0) }) } /// Converts all values to Float using NSNumber.floatValue. Signals a fatal error if a value isn't an NSNumber. var asFloat: AnyUpdatableValue { return self.map(toFloat, inverse: { NSNumber(value: $0) }) } /// Converts all values to Double using NSNumber.doubleValue. Signals a fatal error if a value isn't an NSNumber. var asDouble: AnyUpdatableValue { return self.map(toDouble, inverse: { NSNumber(value: $0) }) } /// Converts all values to CGFloat using NSNumber.doubleValue. Signals a fatal error if a value isn't an NSNumber. var asCGFloat: AnyUpdatableValue { return self.map(toCGFloat, inverse: { NSNumber(value: Double($0)) }) } /// Converts all values to CGPoint using NSValue.CGPointValue. Signals a fatal error if a value isn't an NSValue. var asCGPoint: AnyUpdatableValue { return self.map(toCGPoint, inverse: { #if os(macOS) return NSValue(point: $0) #else return NSValue(cgPoint: $0) #endif }) } /// Converts all values to CGSize using NSValue.CGSizeValue. Signals a fatal error if a value isn't an NSValue. var asCGSize: AnyUpdatableValue { return self.map(toCGSize, inverse: { #if os(macOS) return NSValue(size: $0) #else return NSValue(cgSize: $0) #endif }) } /// Converts all values to CGRect using NSValue.CGRectValue. Signals a fatal error if a value isn't an NSValue. var asCGRect: AnyUpdatableValue { return self.map(toCGRect, inverse: { #if os(macOS) return NSValue(rect: $0) #else return NSValue(cgRect: $0) #endif }) } /// Converts all values to CGAffineTransformValue using NSValue.CGAffineTransformValue. Signals a fatal error if a value isn't an NSValue. var asCGAffineTransform: AnyUpdatableValue { return self.map(toCGAffineTransform, inverse: { #if os(macOS) var transform = $0 return NSValue(bytes: &transform, objCType: encodedCGAffineTransform) #else return NSValue(cgAffineTransform: $0) #endif }) } } ================================================ FILE: Sources/UIBarButtonItem Extensions.swift ================================================ // // UIBarButtonItem Extensions.swift // GlueKit // // Created by Károly Lőrentey on 2016-04-09. // Copyright © 2015–2017 Károly Lőrentey. // #if os(iOS) import UIKit private var associatedObjectKey: UInt8 = 0 private let listenerAction = #selector(TargetActionListener.actionDidFire) extension UIBarButtonItem { public var actionSource: AnySource { if let target = objc_getAssociatedObject(self, &associatedObjectKey) as? TargetActionListener { return target.signal.anySource } let target = TargetActionListener() self.target = target self.action = listenerAction objc_setAssociatedObject(self, &associatedObjectKey, target, .OBJC_ASSOCIATION_RETAIN) return target.signal.anySource } public convenience init(barButtonSystemItem systemItem: UIBarButtonSystemItem, actionBlock: (() -> ())? = nil) { let target = TargetActionListener() self.init(barButtonSystemItem: systemItem, target: target, action: listenerAction) objc_setAssociatedObject(self, &associatedObjectKey, target, .OBJC_ASSOCIATION_RETAIN) if let actionBlock = actionBlock { self.glue.connector.connect(target.signal, to: actionBlock) } } public convenience init(image: UIImage?, style: UIBarButtonItemStyle, actionBlock: (() -> ())? = nil) { let target = TargetActionListener() self.init(image: image, style: style, target: target, action: listenerAction) objc_setAssociatedObject(self, &associatedObjectKey, target, .OBJC_ASSOCIATION_RETAIN) if let actionBlock = actionBlock { self.glue.connector.connect(target.signal, to: actionBlock) } } public convenience init(image: UIImage?, landscapeImagePhone: UIImage?, style: UIBarButtonItemStyle, actionBlock: (() -> ())? = nil) { let target = TargetActionListener() self.init(image: image, landscapeImagePhone: landscapeImagePhone, style: style, target: target, action: listenerAction) objc_setAssociatedObject(self, &associatedObjectKey, target, .OBJC_ASSOCIATION_RETAIN) if let actionBlock = actionBlock { self.glue.connector.connect(target.signal, to: actionBlock) } } public convenience init(title: String?, style: UIBarButtonItemStyle, actionBlock: (() -> ())? = nil) { let target = TargetActionListener() self.init(title: title, style: style, target: target, action: listenerAction) objc_setAssociatedObject(self, &associatedObjectKey, target, .OBJC_ASSOCIATION_RETAIN) if let actionBlock = actionBlock { self.glue.connector.connect(target.signal, to: actionBlock) } } } private class TargetActionListener: NSObject { let signal = Signal() @objc func actionDidFire() { signal.send() } } #endif ================================================ FILE: Sources/UIControl Glue.swift ================================================ // // UIControl Glue.swift // GlueKit // // Created by Károly Lőrentey on 2016-03-11. // Copyright © 2015–2017 Károly Lőrentey. // #if os(iOS) import UIKit import SipHash private var associatedObjectKey: Int8 = 0 extension UIControl { open override var glue: GlueForUIControl { return _glue() } } open class GlueForUIControl: GlueForNSObject { private struct ControlEventsTargetKey: SipHashable { let sink: AnySink let events: UIControlEvents func appendHashes(to hasher: inout SipHasher) { hasher.append(sink) hasher.append(events.rawValue) } static func ==(left: ControlEventsTargetKey, right: ControlEventsTargetKey) -> Bool { return left.sink == right.sink && left.events == right.events } } private final class ControlEventsTarget: NSObject { let sink: AnySink init(sink: AnySink) { self.sink = sink } @objc func eventDidTrigger(_ sender: AnyObject, forEvent event: UIEvent) { sink.receive(event) } } public struct ControlEventsSource: SourceType { public typealias Value = UIEvent public let control: UIControl public let events: UIControlEvents public func add(_ sink: Sink) where Sink.Value == Value { let target = control.glue.add(sink.anySink, for: events) control.addTarget(target, action: #selector(ControlEventsTarget.eventDidTrigger(_:forEvent:)), for: events) } public func remove(_ sink: Sink) -> Sink where Sink.Value == Value { let target = control.glue.remove(sink.anySink, for: events) control.removeTarget(target, action: #selector(ControlEventsTarget.eventDidTrigger(_:forEvent:)), for: events) return target.sink.opened()! } } private var object: UIControl { return owner as! UIControl } private var targets: [ControlEventsTargetKey: ControlEventsTarget] = [:] public func source(for events: UIControlEvents = .primaryActionTriggered) -> ControlEventsSource { return ControlEventsSource(control: object, events: events) } private func add(_ sink: AnySink, for events: UIControlEvents) -> ControlEventsTarget { let target = ControlEventsTarget(sink: sink) let key = ControlEventsTargetKey(sink: sink, events: events) precondition(targets[key] == nil) targets[key] = target return target } private func remove(_ sink: AnySink, for events: UIControlEvents) -> ControlEventsTarget { let key = ControlEventsTargetKey(sink: sink, events: events) let target = targets[key]! targets[key] = nil return target } } #endif ================================================ FILE: Sources/UIDevice Glue.swift ================================================ // // UIDevice Glue.swift // GlueKit // // Created by Károly Lőrentey on 2016-03-13. // Copyright © 2015–2017 Károly Lőrentey. // #if os(iOS) import UIKit extension UIDevice { open override var glue: GlueForUIDevice { return _glue() } } open class GlueForUIDevice: GlueForNSObject { private var object: UIDevice { return owner as! UIDevice } public lazy var orientation: AnyObservableValue = ObservableDeviceOrientation(self.object).anyObservableValue public lazy var batteryState: AnyObservableValue<(UIDeviceBatteryState, Float)> = ObservableBatteryState(self.object).anyObservableValue public lazy var proximityState: AnyObservableValue = ObservableDeviceProximity(self.object).anyObservableValue } private struct DeviceOrientationSink: UniqueOwnedSink { typealias Owner = ObservableDeviceOrientation unowned let owner: Owner func receive(_ notification: Notification) { owner.receive(notification) } } private final class ObservableDeviceOrientation: _BaseObservableValue { unowned let device: UIDevice var orientation: UIDeviceOrientation? = nil init(_ device: UIDevice) { self.device = device } override var value: UIDeviceOrientation { return device.orientation } lazy var notificationSource: AnySource = NotificationCenter.default.glue.source(forName: .UIDeviceOrientationDidChange, sender: self.device, queue: OperationQueue.main) func receive(_ notification: Notification) { beginTransaction() let old = orientation! let new = device.orientation orientation = new sendChange(.init(from: old, to: new)) endTransaction() } override func activate() { device.beginGeneratingDeviceOrientationNotifications() orientation = device.orientation notificationSource.add(DeviceOrientationSink(owner: self)) } override func deactivate() { notificationSource.remove(DeviceOrientationSink(owner: self)) device.endGeneratingDeviceOrientationNotifications() orientation = nil } } private struct BatteryStateSink: UniqueOwnedSink { typealias Owner = ObservableBatteryState unowned let owner: Owner func receive(_ notification: Notification) { owner.receive(notification) } } private var batteryKey: UInt8 = 0 private final class ObservableBatteryState: _BaseObservableValue<(UIDeviceBatteryState, Float)> { typealias Value = (UIDeviceBatteryState, Float) unowned let device: UIDevice var state: Value? = nil var didEnableBatteryMonitoring = false lazy var batteryStateSource: AnySource = NotificationCenter.default.glue.source(forName: .UIDeviceBatteryStateDidChange, sender: self.device, queue: OperationQueue.main) lazy var batteryLevelSource: AnySource = NotificationCenter.default.glue.source(forName: .UIDeviceBatteryLevelDidChange, sender: self.device, queue: OperationQueue.main) init(_ device: UIDevice) { self.device = device } override var value: Value { return (device.batteryState, device.batteryLevel) } func receive(_ notification: Notification) { let old = state! let new = (device.batteryState, device.batteryLevel) if old != new { beginTransaction() state = new sendChange(.init(from: old, to: new)) endTransaction() } } override func activate() { if !device.isBatteryMonitoringEnabled { device.isBatteryMonitoringEnabled = true didEnableBatteryMonitoring = true } state = (device.batteryState, device.batteryLevel) batteryStateSource.add(BatteryStateSink(owner: self)) batteryLevelSource.add(BatteryStateSink(owner: self)) } override func deactivate() { batteryStateSource.remove(BatteryStateSink(owner: self)) batteryLevelSource.remove(BatteryStateSink(owner: self)) if didEnableBatteryMonitoring { device.isBatteryMonitoringEnabled = false didEnableBatteryMonitoring = false } state = nil } } private struct DeviceProximitySink: UniqueOwnedSink { typealias Owner = ObservableDeviceProximity unowned let owner: Owner func receive(_ notification: Notification) { owner.receive(notification) } } private var proximityKey: UInt8 = 0 private final class ObservableDeviceProximity: _BaseObservableValue { unowned let device: UIDevice var state: Bool? = nil var didEnableProximityMonitoring = false lazy var notificationSource: AnySource = NotificationCenter.default.glue.source(forName: .UIDeviceProximityStateDidChange, sender: self.device, queue: OperationQueue.main) init(_ device: UIDevice) { self.device = device } override var value: Bool { return device.proximityState } func receive(_ notification: Notification) { beginTransaction() let old = state! let new = device.proximityState state = new sendChange(.init(from: old, to: new)) endTransaction() } override func activate() { if !device.isProximityMonitoringEnabled { device.isProximityMonitoringEnabled = true didEnableProximityMonitoring = true } state = device.proximityState notificationSource.add(DeviceProximitySink(owner: self)) } override func deactivate() { notificationSource.remove(DeviceProximitySink(owner: self)) if didEnableProximityMonitoring { device.isProximityMonitoringEnabled = false didEnableProximityMonitoring = false } state = nil } } #endif ================================================ FILE: Sources/UIGestureRecognizer Glue.swift ================================================ // // UIGestureRecognizer Glue.swift // GlueKit // // Created by Károly Lőrentey on 2016-03-16. // Copyright © 2015–2017 Károly Lőrentey. // #if os(iOS) import UIKit extension UIGestureRecognizer { open override var glue: GlueForUIGestureRecognizer { return _glue() } } open class GlueForUIGestureRecognizer: GlueForNSObject { private var object: UIGestureRecognizer { return owner as! UIGestureRecognizer } public lazy var state: AnyObservableValue = ObservableGestureRecognizerState(self.object).anyObservableValue } private class ObservableGestureRecognizerState: _BaseObservableValue { private unowned let _gestureRecognizer: UIGestureRecognizer private var _value: UIGestureRecognizerState? = nil init(_ gestureRecognizer: UIGestureRecognizer) { _gestureRecognizer = gestureRecognizer } override var value: UIGestureRecognizerState { return _gestureRecognizer.state } override func activate() { _value = _gestureRecognizer.state _gestureRecognizer.addTarget(self, action: #selector(ObservableGestureRecognizerState.gestureRecognizerDidFire)) } override func deactivate() { _gestureRecognizer.removeTarget(self, action: #selector(ObservableGestureRecognizerState.gestureRecognizerDidFire)) _value = nil } @objc func gestureRecognizerDidFire() { beginTransaction() let old = _value! _value = _gestureRecognizer.state sendChange(ValueChange(from: old, to: _value!)) endTransaction() } } #endif ================================================ FILE: Sources/UILabel Glue.swift ================================================ // // UILabel Glue.swift // GlueKit // // Created by Károly Lőrentey on 2017-04-23. // Copyright © 2015–2017 Károly Lőrentey. // #if os(iOS) import UIKit extension UILabel { open override var glue: GlueForUILabel { return _glue() } } open class GlueForUILabel: GlueForNSObject { private var object: UILabel { return owner as! UILabel } public lazy var text: DependentValue = DependentValue { [unowned self] in self.object.text = $0 } public lazy var textColor: DependentValue = DependentValue { [unowned self] in self.object.textColor = $0 } public lazy var font: DependentValue = DependentValue { [unowned self] in self.object.font = $0 } public lazy var textAlignment: DependentValue = DependentValue { [unowned self] in self.object.textAlignment = $0 } public lazy var lineBreakMode: DependentValue = DependentValue { [unowned self] in self.object.lineBreakMode = $0 } } #endif ================================================ FILE: Sources/UISearchBar Glue.swift ================================================ // // UISearchBar Glue.swift // GlueKit // // Created by Károly Lőrentey on 2017-05-08. // Copyright © 2015–2017 Károly Lőrentey. // #if os(iOS) import UIKit extension UISearchBar { open override var glue: GlueForUISearchBar { return _glue() } } open class GlueForUISearchBar: GlueForNSObject, UISearchBarDelegate { private var object: UISearchBar { return owner as! UISearchBar } public lazy var text: ComputedUpdatable = ComputedUpdatable( getter: { [unowned self] in self.object.text }, setter: { [unowned self] in self.object.text = $0 }) private lazy var _isEditing = Variable(false) public var isEditing: AnyObservableValue { return _isEditing.anyObservableValue } public required init(owner: NSObject) { super.init(owner: owner) object.delegate = self } public func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { text.refresh() } public func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) { _isEditing.value = true } public func searchBarTextDidEndEditing(_ searchBar: UISearchBar) { _isEditing.value = false } public func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool { return true } public func searchBarShouldEndEditing(_ searchBar: UISearchBar) -> Bool { return true } } #endif ================================================ FILE: Sources/UISwitch Glue.swift ================================================ // // UISwitch Glue.swift // GlueKit // // Created by Károly Lőrentey on 2016-11-12. // Copyright © 2015–2017 Károly Lőrentey. // #if os(iOS) import UIKit extension UISwitch { open override var glue: GlueForUISwitch { return _glue() } } open class GlueForUISwitch: GlueForUIControl { private var object: UISwitch { return owner as! UISwitch } public lazy var isOn: ComputedUpdatable = ComputedUpdatable(getter: { [unowned self] in self.object.isOn }, setter: { [unowned self] in self.object.isOn = $0 }, refreshSource: self.source(for: .valueChanged).mapToVoid()) } #endif ================================================ FILE: Sources/UpdatableArray.swift ================================================ // // UpdatableArray.swift // GlueKit // // Created by Károly Lőrentey on 2015-12-11. // Copyright © 2015–2017 Károly Lőrentey. // //MARK: UpdatableArrayType /// An observable array that you can modify. /// /// Note that while `UpdatableArrayType` and `UpdatableArray` implement some methods from `MutableCollectionType` and /// `RangeRaplacableCollectionType`, protocol conformance is intentionally not declared. /// /// These collection protocols define their methods as mutable, which does not make sense for a generic updatable array, /// which is often a proxy that forwards these methods somewhere else (via some transformations). /// Also, it is not a good idea to do complex in-place manipulations (such as `sortInPlace`) on an array that has observers. /// Instead of `updatableArray.sortInPlace()`, which is not available, consider using /// `updatableArray.value = updatableArray.value.sort()`. The latter will probably be much more efficient. public protocol UpdatableArrayType: ObservableArrayType, UpdatableType { // Required members var value: [Element] { get nonmutating set } func apply(_ update: ArrayUpdate) subscript(index: Int) -> Element { get nonmutating set } subscript(bounds: Range) -> ArraySlice { get nonmutating set } // The following are defined in extensions but may be specialized in implementations: var anyUpdatableValue: AnyUpdatableValue<[Element]> { get } var anyUpdatableArray: AnyUpdatableArray { get } } extension UpdatableArrayType { public func apply(_ update: Update>) { self.apply(update.map { change in ArrayChange(from: change.old, to: change.new) }) } public var anyUpdatableValue: AnyUpdatableValue<[Element]> { return AnyUpdatableValue(getter: { self.value }, apply: self.apply, updates: self.valueUpdates) } public var anyUpdatableArray: AnyUpdatableArray { return AnyUpdatableArray(box: UpdatableArrayBox(self)) } public func replaceSubrange(_ range: Range, with elements: C) where C.Iterator.Element == Element { let old = Array(self[range]) let new = elements as? Array ?? Array(elements) apply(ArrayChange(initialCount: self.count, modification: .replaceSlice(old, at: range.lowerBound, with: new))) } public func append(_ newElement: Element) { let c = count apply(ArrayChange(initialCount: c, modification: .insert(newElement, at: c))) } public func append(contentsOf newElements: C) where C.Iterator.Element == Element { let c = count let new = newElements as? Array ?? Array(newElements) apply(ArrayChange(initialCount: c, modification: .replaceSlice([], at: c, with: new))) } public func insert(_ newElement: Element, at i: Int) { let c = count apply(ArrayChange(initialCount: c, modification: .insert(newElement, at: i))) } public func insert(contentsOf newElements: C, at i: Int) where C.Iterator.Element == Element { let c = count let new = newElements as? Array ?? Array(newElements) apply(ArrayChange(initialCount: c, modification: .replaceSlice([], at: i, with: new))) } @discardableResult public func remove(at index: Int) -> Element { let c = count let old = self[index] apply(ArrayChange(initialCount: c, modification: .remove(old, at: index))) return old } public func removeSubrange(_ subrange: Range) { let c = count let old = Array(self[subrange]) apply(ArrayChange(initialCount: c, modification: .replaceSlice(old, at: subrange.lowerBound, with: []))) } @discardableResult public func removeFirst() -> Element { let c = count let old = self[0] apply(ArrayChange(initialCount: c, modification: .remove(old, at: 0))) return old } public func removeFirst(_ n: Int) { let c = count let old = Array(self[0 ..< n]) apply(ArrayChange(initialCount: c, modification: .replaceSlice(old, at: 0, with: []))) } @discardableResult public func removeLast() -> Element { let c = count let old = self[c - 1] apply(ArrayChange(initialCount: c, modification: .remove(old, at: c - 1))) return old } public func removeLast(_ n: Int) { let c = count let old = Array(self[count - n ..< count]) apply(ArrayChange(initialCount: c, modification: .replaceSlice(old, at: count - n, with: []))) } public func removeAll() { let c = count let old = self.value apply(ArrayChange(initialCount: c, modification: .replaceSlice(old, at: 0, with: []))) } } public struct AnyUpdatableArray: UpdatableArrayType { public typealias Value = [Element] public typealias Change = ArrayChange let box: _AbstractUpdatableArray init(box: _AbstractUpdatableArray) { self.box = box } public init(_ base: Updatable) where Updatable.Element == Element { self = base.anyUpdatableArray } public var isBuffered: Bool { return box.isBuffered } public var count: Int { return box.count } public func add(_ sink: Sink) where Sink.Value == Update> { box.add(sink) } @discardableResult public func remove(_ sink: Sink) -> Sink where Sink.Value == Update> { return box.remove(sink) } public var value: [Element] { get { return box.value } nonmutating set { box.value = newValue } } public subscript(index: Int) -> Element { get { return box[index] } nonmutating set { box[index] = newValue } } public subscript(bounds: Range) -> ArraySlice { get { return box[bounds] } nonmutating set { box[bounds] = newValue } } public func apply(_ update: Update>) { self.box.apply(update) } public var observableCount: AnyObservableValue { return box.observableCount } public var anyObservableValue: AnyObservableValue> { return box.anyObservableValue } public var anyObservableArray: AnyObservableArray { return box.anyObservableArray } public var anyUpdatableValue: AnyUpdatableValue<[Element]> { return box.anyUpdatableValue } public var anyUpdatableArray: AnyUpdatableArray { return self } } open class _AbstractUpdatableArray: _AbstractObservableArray, UpdatableArrayType { open override var value: [Element] { get { abstract() } set { abstract() } } open override subscript(index: Int) -> Element { get { abstract() } set { abstract() } } open override subscript(bounds: Range) -> ArraySlice { get { abstract() } set { abstract() } } open func apply(_ update: ArrayUpdate) { abstract() } open var anyUpdatableValue: AnyUpdatableValue<[Element]> { return AnyUpdatableValue(getter: { self.value }, apply: self.apply, updates: self.valueUpdates) } public final var anyUpdatableArray: AnyUpdatableArray { return AnyUpdatableArray(box: self) } } open class _BaseUpdatableArray: _AbstractUpdatableArray, TransactionalThing { var _signal: TransactionalSignal>? = nil var _transactionCount = 0 func rawApply(_ change: ArrayChange) { abstract() } public final override func add(_ sink: Sink) where Sink.Value == Update> { signal.add(sink) } @discardableResult public final override func remove(_ sink: Sink) -> Sink where Sink.Value == Update> { return signal.remove(sink) } public final override func apply(_ update: Update>) { switch update { case .beginTransaction: self.beginTransaction() case .change(let change): self.rawApply(change) self.sendChange(change) case .endTransaction: self.endTransaction() } } open func activate() { // Do nothing } open func deactivate() { // Do nothing } } internal final class UpdatableArrayBox: _AbstractUpdatableArray { typealias Element = Contents.Element var contents: Contents init(_ contents: Contents) { self.contents = contents } override func apply(_ update: ArrayUpdate) { contents.apply(update) } override var value: [Element] { get { return contents.value } set { contents.value = newValue } } override subscript(index: Int) -> Element { get { return contents[index] } set { contents[index] = newValue } } override subscript(bounds: Range) -> ArraySlice { get { return contents[bounds] } set { contents[bounds] = newValue } } override var isBuffered: Bool { return contents.isBuffered } override var count: Int { return contents.count } override func add(_ sink: Sink) where Sink.Value == Update> { contents.add(sink) } @discardableResult override func remove(_ sink: Sink) -> Sink where Sink.Value == Update> { return contents.remove(sink) } override var observableCount: AnyObservableValue { return contents.observableCount } override var anyObservableValue: AnyObservableValue<[Element]> { return contents.anyObservableValue } override var anyUpdatableValue: AnyUpdatableValue> { return contents.anyUpdatableValue } } ================================================ FILE: Sources/UpdatableSet.swift ================================================ // // UpdatableSet.swift // GlueKit // // Created by Károly Lőrentey on 2016-08-13. // Copyright © 2015–2017 Károly Lőrentey. // public protocol UpdatableSetType: ObservableSetType, UpdatableType { var value: Base { get nonmutating set } func apply(_ update: SetUpdate) // Optional members func remove(_ member: Element) func insert(_ member: Element) func removeAll() func formUnion(_ other: Set) func formIntersection(_ other: Set) func formSymmetricDifference(_ other: Set) func subtract(_ other: Set) var anyUpdatableValue: AnyUpdatableValue> { get } var anyUpdatableSet: AnyUpdatableSet { get } } extension UpdatableSetType { public func remove(_ member: Element) { // Note: This should be kept in sync with the same member in _AbstractUpdatableSet. if contains(member) { apply(SetChange(removed: [member])) } } public func insert(_ member: Element) { // Note: This should be kept in sync with the same member in _AbstractUpdatableSet. if !contains(member) { apply(SetChange(inserted: [member])) } } public func removeAll() { // Note: This should be kept in sync with the same member in _AbstractUpdatableSet. if !isEmpty { apply(SetChange(removed: self.value)) } } public func formUnion(_ other: Set) { // Note: This should be kept in sync with the same member in _AbstractUpdatableSet. let difference = other.subtracting(value) self.apply(SetChange(inserted: difference)) } public func formIntersection(_ other: Set) { // Note: This should be kept in sync with the same member in _AbstractUpdatableSet. let difference = value.subtracting(other) self.apply(SetChange(removed: difference)) } public func formSymmetricDifference(_ other: Set) { // Note: This should be kept in sync with the same member in _AbstractUpdatableSet. let value = self.value let intersection = value.intersection(other) let additions = other.subtracting(value) self.apply(SetChange(removed: intersection, inserted: additions)) } public func subtract(_ other: Set) { // Note: This should be kept in sync with the same member in _AbstractUpdatableSet. let intersection = value.intersection(other) self.apply(SetChange(removed: intersection)) } public func apply(_ update: ValueUpdate>) { self.apply(update.map { change in SetChange(from: change.old, to: change.new) }) } public var anyUpdatableValue: AnyUpdatableValue> { return AnyUpdatableValue( getter: { self.value }, apply: self.apply, updates: self.valueUpdates) } public var anyUpdatableSet: AnyUpdatableSet { return AnyUpdatableSet(box: UpdatableSetBox(self)) } } public struct AnyUpdatableSet: UpdatableSetType { public typealias Value = Set public typealias Base = Set public typealias Change = SetChange let box: _AbstractUpdatableSet init(box: _AbstractUpdatableSet) { self.box = box } public var isBuffered: Bool { return box.isBuffered } public var count: Int { return box.count } public var value: Set { get { return box.value } nonmutating set { box.value = newValue } } public func contains(_ member: Element) -> Bool { return box.contains(member) } public func isSubset(of other: Set) -> Bool { return box.isSubset(of: other) } public func isSuperset(of other: Set) -> Bool { return box.isSuperset(of: other) } public func apply(_ update: SetUpdate) { box.apply(update) } public func remove(_ member: Element) { box.remove(member) } public func insert(_ member: Element) { box.insert(member) } public func removeAll() { box.removeAll() } public func formUnion(_ other: Set) { box.formUnion(other) } public func formIntersection(_ other: Set) { box.formIntersection(other) } public func formSymmetricDifference(_ other: Set) { box.formSymmetricDifference(other) } public func subtract(_ other: Set) { box.subtract(other) } public func add(_ sink: Sink) where Sink.Value == Update> { box.add(sink) } @discardableResult public func remove(_ sink: Sink) -> Sink where Sink.Value == Update> { return box.remove(sink) } public var observableCount: AnyObservableValue { return box.observableCount } public var anyObservableValue: AnyObservableValue> { return box.anyObservableValue } public var anyObservableSet: AnyObservableSet { return box.anyObservableSet } public var anyUpdatableValue: AnyUpdatableValue> { return box.anyUpdatableValue } public var anyUpdatableSet: AnyUpdatableSet { return self } } open class _AbstractUpdatableSet: _AbstractObservableSet, UpdatableSetType { open override var value: Set { get { abstract() } set { abstract() } } open func apply(_ update: SetUpdate) { abstract() } open func remove(_ member: Element) { // Note: This should be kept in sync with the same member in the UpdatableSetType extension above. if contains(member) { apply(SetChange(removed: [member])) } } open func insert(_ member: Element) { // Note: This should be kept in sync with the same member in the UpdatableSetType extension above. if !contains(member) { apply(SetChange(inserted: [member])) } } open func removeAll() { // Note: This should be kept in sync with the same member in the UpdatableSetType extension above. if !isEmpty { apply(SetChange(removed: self.value)) } } open func formUnion(_ other: Set) { // Note: This should be kept in sync with the same member in the UpdatableSetType extension above. let difference = other.subtracting(value) self.apply(SetChange(inserted: difference)) } open func formIntersection(_ other: Set) { // Note: This should be kept in sync with the same member in the UpdatableSetType extension above. let difference = value.subtracting(other) self.apply(SetChange(removed: difference)) } open func formSymmetricDifference(_ other: Set) { // Note: This should be kept in sync with the same member in the UpdatableSetType extension above. let value = self.value let intersection = value.intersection(other) let additions = other.subtracting(value) self.apply(SetChange(removed: intersection, inserted: additions)) } open func subtract(_ other: Set) { // Note: This should be kept in sync with the same member in the UpdatableSetType extension above. let intersection = value.intersection(other) self.apply(SetChange(removed: intersection)) } open var anyUpdatableValue: AnyUpdatableValue> { return AnyUpdatableValue( getter: { self.value }, apply: self.apply, updates: self.valueUpdates) } public final var anyUpdatableSet: AnyUpdatableSet { return AnyUpdatableSet(box: self) } } open class _BaseUpdatableSet: _AbstractUpdatableSet, TransactionalThing { public typealias Change = SetChange var _signal: TransactionalSignal>? = nil var _transactionCount: Int = 0 func rawApply(_ change: Change) { abstract() } public final override func add(_ sink: Sink) where Sink.Value == Update { signal.add(sink) } @discardableResult public final override func remove(_ sink: Sink) -> Sink where Sink.Value == Update { return signal.remove(sink) } public final override func apply(_ update: Update) { switch update { case .beginTransaction: beginTransaction() case .change(let change): rawApply(change) sendChange(change) case .endTransaction: endTransaction() } } open func activate() { // Do nothing } open func deactivate() { // Do nothing } } final class UpdatableSetBox: _AbstractUpdatableSet { typealias Element = Contents.Element typealias Change = SetChange let contents: Contents init(_ contents: Contents) { self.contents = contents } override var isBuffered: Bool { return contents.isBuffered } override var count: Int { return contents.count } override var value: Set { get { return contents.value } set { contents.value = newValue } } override func apply(_ update: SetUpdate) { contents.apply(update) } override func remove(_ member: Element) { contents.remove(member) } override func insert(_ member: Element) { contents.insert(member) } override func removeAll() { contents.removeAll() } override func formUnion(_ other: Set) { contents.formUnion(other) } override func formIntersection(_ other: Set) { contents.formIntersection(other) } override func formSymmetricDifference(_ other: Set) { contents.formSymmetricDifference(other) } override func subtract(_ other: Set) { contents.subtract(other) } override func contains(_ member: Element) -> Bool { return contents.contains(member) } override func isSubset(of other: Set) -> Bool { return contents.isSubset(of: other) } override func isSuperset(of other: Set) -> Bool { return contents.isSuperset(of: other) } override func add(_ sink: Sink) where Sink.Value == Update { contents.add(sink) } @discardableResult override func remove(_ sink: Sink) -> Sink where Sink.Value == Update { return contents.remove(sink) } override var observableCount: AnyObservableValue { return contents.observableCount } override var anyObservableValue: AnyObservableValue> { return contents.anyObservableValue } override var anyUpdatableValue: AnyUpdatableValue> { return contents.anyUpdatableValue } } ================================================ FILE: Sources/UpdatableValue.swift ================================================ // // Updatable.swift // GlueKit // // Created by Károly Lőrentey on 2015-12-07. // Copyright © 2015–2017 Károly Lőrentey. // /// An observable thing that also includes support for updating its value. public protocol UpdatableValueType: ObservableValueType, UpdatableType { /// Returns the type-erased version of this UpdatableValueType. var anyUpdatableValue: AnyUpdatableValue { get } } extension UpdatableValueType { /// Returns the type-erased version of this UpdatableValueType. public var anyUpdatableValue: AnyUpdatableValue { return AnyUpdatableValue(self) } } /// The type erased representation of an UpdatableValueType. public struct AnyUpdatableValue: UpdatableValueType { public typealias Change = ValueChange private let box: _AbstractUpdatableValue init(box: _AbstractUpdatableValue) { self.box = box } public init(getter: @escaping () -> Value, apply: @escaping (Update>) -> Void, updates: Updates) where Updates.Value == Update { self.box = UpdatableClosureBox(getter: getter, apply: apply, updates: updates) } public init(_ base: Base) where Base.Value == Value { self.box = UpdatableBox(base) } public var value: Value { get { return box.value } nonmutating set { box.value = newValue } } public func apply(_ update: Update) { box.apply(update) } public func add(_ sink: Sink) where Sink.Value == Update { box.add(sink) } @discardableResult public func remove(_ sink: Sink) -> Sink where Sink.Value == Update { return box.remove(sink) } public var anyObservableValue: AnyObservableValue { return box.anyObservableValue } public var anyUpdatableValue: AnyUpdatableValue { return self } } open class _AbstractUpdatableValue: _AbstractObservableValue, UpdatableValueType { public typealias Change = ValueChange open override var value: Value { get { abstract() } set { abstract() } } open func apply(_ update: Update) { abstract() } public final var anyUpdatableValue: AnyUpdatableValue { return AnyUpdatableValue(box: self) } } open class _BaseUpdatableValue: _AbstractUpdatableValue, TransactionalThing { var _signal: TransactionalSignal>? = nil var _transactionCount = 0 func rawGetValue() -> Value { abstract() } func rawSetValue(_ value: Value) { abstract() } public final override func add(_ sink: Sink) where Sink.Value == Update { signal.add(sink) } @discardableResult public final override func remove(_ sink: Sink) -> Sink where Sink.Value == Update { return signal.remove(sink) } public final override var value: Value { get { return rawGetValue() } set { beginTransaction() let old = rawGetValue() rawSetValue(newValue) sendChange(ValueChange(from: old, to: newValue)) endTransaction() } } public final override func apply(_ update: Update) { switch update { case .beginTransaction: beginTransaction() case .change(let change): rawSetValue(change.new) sendChange(change) case .endTransaction: endTransaction() } } open func activate() { // Do nothing } open func deactivate() { // Do nothing } } internal final class UpdatableBox: _AbstractUpdatableValue { typealias Value = Base.Value private let base: Base init(_ base: Base) { self.base = base } override var value: Value { get { return base.value } set { base.value = newValue } } override func apply(_ update: Update>) { base.apply(update) } override func add(_ sink: Sink) where Sink.Value == Update { base.add(sink) } @discardableResult override func remove(_ sink: Sink) -> Sink where Sink.Value == Update { return base.remove(sink) } } private final class UpdatableClosureBox: _AbstractUpdatableValue where Updates.Value == Update> { /// The getter closure for the current value of this updatable. private let _getter: () -> Value private let _apply: (Update>) -> Void /// A closure returning a source providing the values of future updates to this updatable. private let _updates: Updates public init(getter: @escaping () -> Value, apply: @escaping (Update>) -> Void, updates: Updates) { self._getter = getter self._apply = apply self._updates = updates } override var value: Value { get { return _getter() } set { _apply(.beginTransaction) _apply(.change(ValueChange(from: _getter(), to: newValue))) _apply(.endTransaction) } } override func apply(_ update: Update>) { _apply(update) } override func add(_ sink: Sink) where Sink.Value == Update { _updates.add(sink) } @discardableResult override func remove(_ sink: Sink) -> Sink where Sink.Value == Update { return _updates.remove(sink) } } ================================================ FILE: Sources/Update.swift ================================================ // // Update.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-26. // Copyright © 2015–2017 Károly Lőrentey. // /// Updates are events that describe a change that is happening to an observable. /// Observables only change inside transactions. A transaction consists three phases, represented /// by the three cases of this enum type: /// /// - `beginTransaction` signals the start of a new transaction. /// - `change` describes a (partial) change to the value of the observable. /// Each transaction may include any number of such changes. /// - `endTransaction` closes the transaction. /// /// While a transaction is in progress, the value of an observable includes all changes that have already been /// reported in updates. /// /// Note that is perfectly legal for a transaction to include no actual changes. public enum Update { public typealias Value = Change.Value /// Hang on, I feel a change coming up. case beginTransaction /// Here is one change, but I think there might be more coming. case change(Change) /// OK, I'm done changing. case endTransaction } extension Update { public var change: Change? { if case let .change(change) = self { return change } return nil } public func filter(_ test: (Change) -> Bool) -> Update? { switch self { case .beginTransaction, .endTransaction: return self case .change(let change): if test(change) { return self } return nil } } public func map(_ transform: (Change) -> Result) -> Update { switch self { case .beginTransaction: return .beginTransaction case .change(let change): return .change(transform(change)) case .endTransaction: return .endTransaction } } public func flatMap(_ transform: (Change) -> Result?) -> Update? { switch self { case .beginTransaction: return .beginTransaction case .change(let change): guard let new = transform(change) else { return nil } return .change(new) case .endTransaction: return .endTransaction } } } ================================================ FILE: Sources/ValueChange.swift ================================================ // // ValueChange.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-04. // Copyright © 2015–2017 Károly Lőrentey. // /// A simple change description that includes a snapshot of the value before and after the change. public struct ValueChange: ChangeType { public var old: Value public var new: Value public init(from old: Value, to new: Value) { self.old = old self.new = new } public var isEmpty: Bool { // There is no way to compare old and new at this level. return false } public func apply(on value: inout Value) { value = new } public func applied(on value: Value) -> Value { return new } public mutating func merge(with next: ValueChange) { self.new = next.new } public func merged(with next: ValueChange) -> ValueChange { return .init(from: old, to: next.new) } public func reversed() -> ValueChange { return .init(from: new, to: old) } public func map(_ transform: (Value) -> R) -> ValueChange { return .init(from: transform(old), to: transform(new)) } } extension ValueChange: CustomStringConvertible { public var description: String { return "\(old) -> \(new)" } } public func ==(a: ValueChange, b: ValueChange) -> Bool { return a.old == b.old && a.new == b.new } public func !=(a: ValueChange, b: ValueChange) -> Bool { return !(a == b) } ================================================ FILE: Sources/ValueMappingForArrayField.swift ================================================ // // ValueMappingForArrayField.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-09. // Copyright © 2015–2017 Károly Lőrentey. // extension ObservableValueType { /// Map is an operator that implements key path coding and observing. /// Given an observable parent and a key that selects an observable child component (a.k.a "field") of its value, /// `map` returns a new observable that can be used to look up and modify the field and observe its changes /// indirectly through the parent. If the field is an observable array, then the result will be, too. /// /// @param key: An accessor function that returns a component of self (a field) that is an observable array. /// @returns A new observable array that tracks changes to both self and the field returned by `key`. /// /// For example, take the model for a hypothetical group chat system below. /// ``` /// class Account { /// let name: Variable /// let avatar: Variable /// } /// class Message { /// let author: Variable /// let text: Variable /// } /// class Room { /// let latestMessage: AnyObservableValue /// let messages: ArrayVariable /// let newMessages: Source /// } /// let currentRoom: Variable /// ``` /// /// You can create an observable array for all messages in the current room with /// ```Swift /// let observable = currentRoom.map{$0.messages} /// ``` /// Sinks connected to `observable.changes` will fire whenever the current room changes, or when the list of /// messages is updated in the current room. The observable can also be used to simply retrieve the list of messages /// at any time. /// public func map(_ key: @escaping (Value) -> Field) -> AnyObservableArray { return ValueMappingForArrayField(parent: self, key: key).anyObservableArray } public func map(_ key: @escaping (Value) -> Field) -> AnyUpdatableArray { return ValueMappingForUpdatableArrayField(parent: self, key: key).anyUpdatableArray } } /// A source of changes for an AnyObservableArray field. private final class UpdateSourceForArrayField : TransactionalSource> { typealias Element = Field.Element typealias Base = [Element] typealias Change = ArrayChange private struct ParentSink: OwnedSink { typealias Owner = UpdateSourceForArrayField unowned let owner: Owner let identifier = 1 func receive(_ update: ValueUpdate) { owner.applyParentUpdate(update) } } private struct FieldSink: OwnedSink { typealias Owner = UpdateSourceForArrayField unowned let owner: Owner let identifier = 2 func receive(_ update: ArrayUpdate) { owner.applyFieldUpdate(update) } } let parent: Parent let key: (Parent.Value) -> Field private var field: Field? = nil init(parent: Parent, key: @escaping (Parent.Value) -> Field) { self.parent = parent self.key = key } override func activate() { let field = key(parent.value) parent.add(ParentSink(owner: self)) field.add(FieldSink(owner: self)) self.field = field } override func deactivate() { parent.remove(ParentSink(owner: self)) field!.remove(FieldSink(owner: self)) field = nil } func applyParentUpdate(_ update: ValueUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): let old = key(change.old).value let field = self.key(change.new) self.field!.remove(FieldSink(owner: self)) self.field = field field.add(FieldSink(owner: self)) sendChange(.init(from: old, to: field.value)) case .endTransaction: endTransaction() } } func applyFieldUpdate(_ update: ArrayUpdate) { signal.send(update) } } private final class ValueMappingForArrayField: _AbstractObservableArray { typealias Element = Field.Element typealias Change = ArrayChange private let updateSource: UpdateSourceForArrayField init(parent: Parent, key: @escaping (Parent.Value) -> Field) { updateSource = UpdateSourceForArrayField(parent: parent, key: key) } var parent: Parent { return updateSource.parent } var key: (Parent.Value) -> Field { return updateSource.key } var field: Field { return updateSource.key(updateSource.parent.value) } override var isBuffered: Bool { return field.isBuffered } override subscript(_ index: Int) -> Element { return field[index] } override subscript(_ range: Range) -> ArraySlice { return field[range] } override var value: Array { return field.value } override var count: Int { return field.count } override var observableCount: AnyObservableValue { return parent.map { self.key($0).observableCount } } override func add(_ sink: Sink) where Sink.Value == Update { updateSource.add(sink) } @discardableResult override func remove(_ sink: Sink) -> Sink where Sink.Value == Update { return updateSource.remove(sink) } } private final class ValueMappingForUpdatableArrayField: _AbstractUpdatableArray { typealias Element = Field.Element typealias Change = ArrayChange let updateSource: UpdateSourceForArrayField init(parent: Parent, key: @escaping (Parent.Value) -> Field) { updateSource = UpdateSourceForArrayField(parent: parent, key: key) } var parent: Parent { return updateSource.parent } var key: (Parent.Value) -> Field { return updateSource.key } var field: Field { return updateSource.key(updateSource.parent.value) } override var isBuffered: Bool { return field.isBuffered } override subscript(_ index: Int) -> Element { get { return field[index] } set { field[index] = newValue } } override subscript(_ range: Range) -> ArraySlice { get { return field[range] } set { field[range] = newValue } } override var value: Array { get { return field.value } set { field.value = newValue } } override var count: Int { return field.count } override var observableCount: AnyObservableValue { return parent.map { self.key($0).observableCount } } override func apply(_ update: Update>) { field.apply(update) } override func add(_ sink: Sink) where Sink.Value == Update { updateSource.add(sink) } @discardableResult override func remove(_ sink: Sink) -> Sink where Sink.Value == Update { return updateSource.remove(sink) } } ================================================ FILE: Sources/ValueMappingForSetField.swift ================================================ // // ValueMappingForSetField.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-09. // Copyright © 2015–2017 Károly Lőrentey. // extension ObservableValueType { public func map(_ key: @escaping (Value) -> Field) -> AnyObservableSet { return ValueMappingForSetField(parent: self, key: key).anyObservableSet } public func map(_ key: @escaping (Value) -> Field) -> AnyUpdatableSet { return ValueMappingForUpdatableSetField(parent: self, key: key).anyUpdatableSet } } private final class UpdateSourceForSetField: TransactionalSource> { typealias Element = Field.Element typealias Change = SetChange typealias Value = Update private struct ParentSink: OwnedSink { typealias Owner = UpdateSourceForSetField unowned let owner: Owner let identifier = 1 func receive(_ update: ValueUpdate) { owner.applyParentUpdate(update) } } private struct FieldSink: OwnedSink { typealias Owner = UpdateSourceForSetField unowned let owner: Owner let identifier = 2 func receive(_ update: SetUpdate) { owner.applyFieldUpdate(update) } } let parent: Parent let key: (Parent.Value) -> Field private var _field: Field? = nil init(parent: Parent, key: @escaping (Parent.Value) -> Field) { self.parent = parent self.key = key } fileprivate var field: Field { if let field = self._field { return field } return key(parent.value) } override func activate() { let field = key(parent.value) field.add(FieldSink(owner: self)) parent.add(ParentSink(owner: self)) _field = field } override func deactivate() { parent.remove(ParentSink(owner: self)) _field!.remove(FieldSink(owner: self)) _field = nil } private func subscribe(to field: Field) { _field!.remove(FieldSink(owner: self)) _field = field field.add(FieldSink(owner: self)) } func applyParentUpdate(_ update: ValueUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): let oldValue = self._field!.value let field = self.key(change.new) self.subscribe(to: field) sendChange(SetChange(removed: oldValue, inserted: field.value)) case .endTransaction: endTransaction() } } func applyFieldUpdate(_ update: SetUpdate) { send(update) } } private final class ValueMappingForSetField: _AbstractObservableSet { typealias Element = Field.Element private let updateSource: UpdateSourceForSetField init(parent: Parent, key: @escaping (Parent.Value) -> Field) { self.updateSource = .init(parent: parent, key: key) } var field: Field { return updateSource.field } override var isBuffered: Bool { return field.isBuffered } override var count: Int { return field.count } override var value: Set { return field.value } override func contains(_ member: Element) -> Bool { return field.contains(member) } override func isSubset(of other: Set) -> Bool { return field.isSubset(of: other) } override func isSuperset(of other: Set) -> Bool { return field.isSuperset(of: other) } override func add(_ sink: Sink) where Sink.Value == Update { updateSource.add(sink) } @discardableResult override func remove(_ sink: Sink) -> Sink where Sink.Value == Update { return updateSource.remove(sink) } } private final class ValueMappingForUpdatableSetField: _AbstractUpdatableSet { typealias Element = Field.Element private let updateSource: UpdateSourceForSetField init(parent: Parent, key: @escaping (Parent.Value) -> Field) { self.updateSource = .init(parent: parent, key: key) } var field: Field { return updateSource.field } override var isBuffered: Bool { return field.isBuffered } override var count: Int { return field.count } override var value: Set { get { return field.value } set { field.value = newValue } } override func contains(_ member: Element) -> Bool { return field.contains(member) } override func isSubset(of other: Set) -> Bool { return field.isSubset(of: other) } override func isSuperset(of other: Set) -> Bool { return field.isSuperset(of: other) } override func apply(_ update: SetUpdate) { field.apply(update) } override func remove(_ member: Element) { field.remove(member) } override func insert(_ member: Element) { field.insert(member) } override func removeAll() { field.removeAll() } override func formUnion(_ other: Set) { field.formUnion(other) } override func formIntersection(_ other: Set) { field.formIntersection(other) } override func formSymmetricDifference(_ other: Set) { field.formSymmetricDifference(other) } override func subtract(_ other: Set) { field.subtract(other) } final override func add(_ sink: Sink) where Sink.Value == Update { updateSource.add(sink) } @discardableResult final override func remove(_ sink: Sink) -> Sink where Sink.Value == Update { return updateSource.remove(sink) } } ================================================ FILE: Sources/ValueMappingForSourceField.swift ================================================ // // ValueMappingForSourceField.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-09. // Copyright © 2015–2017 Károly Lőrentey. // extension ObservableValueType { /// Map is an operator that implements key path coding and observing. /// Given an observable parent and a key that selects a child component (a.k.a "field") of its value that is a source, /// `map` returns a new source that can be used subscribe to the field indirectly through the parent. /// /// - Parameter key: An accessor function that returns a component of self (a field) that is a SourceType. /// /// - Returns: A new source that sends the same values as the current source returned by key in the parent. public func map(_ key: @escaping (Value) -> Source) -> AnySource { return ValueMappingForSourceField(parent: self, key: key).anySource } } /// A source of values for a Source field. private final class ValueMappingForSourceField: SignalerSource { typealias Value = Field.Value private struct SourceFieldSink: UniqueOwnedSink { typealias Owner = ValueMappingForSourceField unowned let owner: Owner func receive(_ update: ValueUpdate) { owner.applyParentUpdate(update) } } let parent: Parent let key: (Parent.Value) -> Field private var _field: Field? = nil init(parent: Parent, key: @escaping (Parent.Value) -> Field) { self.parent = parent self.key = key } override func activate() { precondition(_field == nil) let field = key(parent.value) _field = field parent.add(SourceFieldSink(owner: self)) field.add(signal.asSink) } override func deactivate() { _field!.remove(signal.asSink) _field = nil parent.remove(SourceFieldSink(owner: self)) } func applyParentUpdate(_ update: ValueUpdate) { switch update { case .beginTransaction: break case .change(let change): let field = key(change.new) _field!.remove(signal.asSink) _field = field field.add(signal.asSink) case .endTransaction: break } } } ================================================ FILE: Sources/ValueMappingForValue.swift ================================================ // // ValueMappingForValue.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-09. // Copyright © 2015–2017 Károly Lőrentey. // public extension ObservableValueType { /// Returns an observable that calculates `transform` on all current and future values of this observable. public func map(_ transform: @escaping (Value) -> Output) -> AnyObservableValue { return ValueMappingForValue(parent: self, transform: transform).anyObservableValue } } private final class ValueMappingForValue: _AbstractObservableValue { let parent: Parent let transform: (Parent.Value) -> Value let sinkTransform: SinkTransformFromMapping, ValueUpdate> init(parent: Parent, transform: @escaping (Parent.Value) -> Value) { self.parent = parent self.transform = transform self.sinkTransform = SinkTransformFromMapping { u in u.map { c in c.map(transform) } } } override var value: Value { return transform(parent.value) } override func add(_ sink: Sink) where Sink.Value == Update { parent.add(TransformedSink(sink: sink, transform: sinkTransform)) } @discardableResult override func remove(_ sink: Sink) -> Sink where Sink.Value == Update { return parent.remove(TransformedSink(sink: sink, transform: sinkTransform)).sink } } extension UpdatableValueType { public func map(_ transform: @escaping (Value) -> Output, inverse: @escaping (Output) -> Value) -> AnyUpdatableValue { return ValueMappingForUpdatableValue(parent: self, transform: transform, inverse: inverse).anyUpdatableValue } } private final class ValueMappingForUpdatableValue: _AbstractUpdatableValue { let parent: Parent let transform: (Parent.Value) -> Value let inverse: (Value) -> Parent.Value let sinkTransform: SinkTransformFromMapping, ValueUpdate> init(parent: Parent, transform: @escaping (Parent.Value) -> Value, inverse: @escaping (Value) -> Parent.Value) { self.parent = parent self.transform = transform self.inverse = inverse self.sinkTransform = SinkTransformFromMapping { u in u.map { c in c.map(transform) } } } override var value: Value { get { return transform(parent.value) } set { parent.value = inverse(newValue) } } override func apply(_ update: Update>) { parent.apply(update.map { change in change.map(inverse) }) } override func add(_ sink: Sink) where Sink.Value == Update { parent.add(TransformedSink(sink: sink, transform: sinkTransform)) } @discardableResult override func remove(_ sink: Sink) -> Sink where Sink.Value == Update { return parent.remove(TransformedSink(sink: sink, transform: sinkTransform)).sink } } ================================================ FILE: Sources/ValueMappingForValueField.swift ================================================ // // SelectOperator.swift // GlueKit // // Created by Károly Lőrentey on 2015-12-06. // Copyright © 2015–2017 Károly Lőrentey. // extension ObservableValueType { /// Map is an operator that implements key path coding and observing. /// Given an observable parent and a key that selects an observable child component (a.k.a "field") of its value, /// `map` returns a new observable that can be used to look up and modify the field and observe its changes /// indirectly through the parent. /// /// @param key: An accessor function that returns a component of self (a field) that is itself observable. /// @returns A new observable that tracks changes to both self and the field returned by `key`. /// /// For example, take the model for a hypothetical group chat system below. /// ``` /// class Account { /// let name: Variable /// let avatar: Variable /// } /// class Message { /// let author: Variable /// let text: Variable /// } /// class Room { /// let latestMessage: AnyObservableValue /// let newMessages: Source /// let messages: ArrayVariable /// } /// let currentRoom: Variable /// ``` /// /// You can create an observable for the latest message in the current room with /// ```Swift /// let observable = currentRoom.map{$0.latestMessage} /// ``` /// Sinks connected to `observable.futureValues` will fire whenever the current room changes, or when a new /// message is posted in the current room. The observable can also be used to simply retrieve the latest /// message at any time. /// public func map(_ key: @escaping (Value) -> O) -> AnyObservableValue { return ValueMappingForValueField(parent: self, key: key).anyObservableValue } } /// A source of changes for an Observable field. private final class ValueMappingForValueField: _BaseObservableValue { typealias Value = Field.Value typealias Change = ValueChange struct ParentSink: OwnedSink { typealias Owner = ValueMappingForValueField unowned let owner: Owner let identifier = 1 func receive(_ update: ValueUpdate) { owner.applyParentUpdate(update) } } struct FieldSink: OwnedSink { typealias Owner = ValueMappingForValueField unowned let owner: Owner let identifier = 2 func receive(_ update: ValueUpdate) { owner.applyFieldUpdate(update) } } let parent: Parent let key: (Parent.Value) -> Field private var currentValue: Field.Value? = nil private var field: Field? = nil override var value: Field.Value { if let v = currentValue { return v } return key(parent.value).value } init(parent: Parent, key: @escaping (Parent.Value) -> Field) { self.parent = parent self.key = key } override func activate() { precondition(currentValue == nil) let field = key(parent.value) self.currentValue = field.value subscribe(to: field) parent.add(ParentSink(owner: self)) } override func deactivate() { precondition(currentValue != nil) self.field!.remove(FieldSink(owner: self)) parent.remove(ParentSink(owner: self)) self.field = nil self.currentValue = nil } private func subscribe(to field: Field) { self.field?.remove(FieldSink(owner: self)) self.field = field field.add(FieldSink(owner: self)) } func applyParentUpdate(_ update: ValueUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): let field = key(change.new) let old = currentValue! let new = field.value currentValue = new sendChange(ValueChange(from: old, to: new)) subscribe(to: field) case .endTransaction: endTransaction() } } func applyFieldUpdate(_ update: ValueUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): let old = currentValue! currentValue = change.new sendChange(ValueChange(from: old, to: change.new)) case .endTransaction: endTransaction() } } } extension ObservableValueType { public func flatMap(_ key: @escaping (Value) -> O?) -> AnyObservableValue { return ValueMappingForOptionalValueField(parent: self, key: key).anyObservableValue } } private final class ValueMappingForOptionalValueField: _BaseObservableValue { typealias Value = Field.Value? typealias Change = ValueChange struct ParentSink: OwnedSink { typealias Owner = ValueMappingForOptionalValueField unowned let owner: Owner let identifier = 1 func receive(_ update: ValueUpdate) { owner.applyParentUpdate(update) } } struct FieldSink: OwnedSink { typealias Owner = ValueMappingForOptionalValueField unowned let owner: Owner let identifier = 2 func receive(_ update: ValueUpdate) { owner.applyFieldUpdate(update) } } let parent: Parent let key: (Parent.Value) -> Field? private var activated = false private var currentValue: Field.Value? = nil private var field: Field? = nil override var value: Value { if activated { return currentValue } return key(parent.value)?.value } init(parent: Parent, key: @escaping (Parent.Value) -> Field?) { self.parent = parent self.key = key } override func activate() { precondition(!activated) activated = true if let field = key(parent.value) { self.currentValue = field.value subscribe(to: field) } else { self.currentValue = nil } parent.add(ParentSink(owner: self)) } override func deactivate() { precondition(activated) self.field?.remove(FieldSink(owner: self)) parent.remove(ParentSink(owner: self)) self.field = nil self.currentValue = nil activated = false } private func subscribe(to field: Field) { self.field?.remove(FieldSink(owner: self)) self.field = field field.add(FieldSink(owner: self)) } func applyParentUpdate(_ update: ValueUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): let field = key(change.new) let old = currentValue let new = field?.value currentValue = new sendChange(ValueChange(from: old, to: new)) if let field = field { subscribe(to: field) } case .endTransaction: endTransaction() } } func applyFieldUpdate(_ update: ValueUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): let old = currentValue currentValue = change.new sendChange(ValueChange(from: old, to: change.new)) case .endTransaction: endTransaction() } } } extension ObservableValueType { /// Map is an operator that implements key path coding and observing. /// Given an observable parent and a key that selects an observable child component (a.k.a "field") of its value, /// `map` returns a new observable that can be used to look up and modify the field and observe its changes /// indirectly through the parent. If the field is updatable, then the result will be, too. /// /// @param key: An accessor function that returns a component of self (a field) that is itself updatable. /// @returns A new updatable that tracks changes to both self and the field returned by `key`. /// /// For example, take the model for a hypothetical group chat system below. /// ``` /// class Account { /// let name: Variable /// let avatar: Variable /// } /// class Message { /// let author: Variable /// let text: Variable /// } /// class Room { /// let latestMessage: AnyObservableValue /// let messages: ArrayVariable /// let newMessages: Source /// } /// let currentRoom: Variable /// ``` /// /// You can create an updatable for the avatar image of the author of the latest message in the current room with /// ```Swift /// let updatable = currentRoom.map{$0.latestMessage}.map{$0.author}.map{$0.avatar} /// ``` /// Sinks connected to `updatable.futureValues` will fire whenever the current room changes, or when a new message is posted /// in the current room, or when the author of that message is changed, or when the current /// author changes their avatar. The updatable can also be used to simply retrieve the avatar at any time, /// or to update it. /// public func map(_ key: @escaping (Value) -> U) -> AnyUpdatableValue { return ValueMappingForUpdatableField(parent: self, key: key).anyUpdatableValue } } private final class ValueMappingForUpdatableField: _AbstractUpdatableValue { typealias Value = Field.Value private let _observable: ValueMappingForValueField init(parent: Parent, key: @escaping (Parent.Value) -> Field) { self._observable = ValueMappingForValueField(parent: parent, key: key) } override var value: Field.Value { get { return _observable.value } set { _observable.key(_observable.parent.value).value = newValue } } override func apply(_ update: Update>) { return _observable.key(_observable.parent.value).apply(update) } override func add(_ sink: Sink) where Sink.Value == Update { _observable.add(sink) } @discardableResult override func remove(_ sink: Sink) -> Sink where Sink.Value == Update { return _observable.remove(sink) } } ================================================ FILE: Sources/ValueReference.swift ================================================ // // ValueReference.swift // GlueKit // // Created by Károly Lőrentey on 2016-11-02. // Copyright © 2015–2017 Károly Lőrentey. // extension ObservableValueType where Value: ObservableValueType { public func unpacked() -> AnyObservableValue { return UnpackedObservableValueReference(self).anyObservableValue } } private final class UnpackedObservableValueReference: _BaseObservableValue where Reference.Value: ObservableValueType { typealias Target = Reference.Value typealias Value = Target.Value typealias Change = ValueChange private struct ReferenceSink: UniqueOwnedSink { typealias Owner = UnpackedObservableValueReference unowned(unsafe) let owner: Owner func receive(_ update: ValueUpdate) { owner.applyReferenceUpdate(update) } } private struct TargetSink: UniqueOwnedSink { typealias Owner = UnpackedObservableValueReference unowned(unsafe) let owner: Owner func receive(_ update: ValueUpdate) { owner.applyTargetUpdate(update) } } private var _reference: Reference private var _target: Reference.Value? = nil // Retained to make sure we keep it alive init(_ reference: Reference) { _reference = reference super.init() } override func activate() { _reference.updates.add(ReferenceSink(owner: self)) let target = _reference.value _target = target target.updates.add(TargetSink(owner: self)) } override func deactivate() { _target!.updates.remove(TargetSink(owner: self)) _reference.updates.remove(ReferenceSink(owner: self)) } func applyReferenceUpdate(_ update: ValueUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): if isConnected { _target!.remove(TargetSink(owner: self)) _target = change.new _target!.add(TargetSink(owner: self)) sendChange(ValueChange(from: change.old.value, to: change.new.value)) } case .endTransaction: endTransaction() } } func applyTargetUpdate(_ update: ValueUpdate) { switch update { case .beginTransaction: beginTransaction() case .change(let change): sendChange(change) case .endTransaction: endTransaction() } } override var value: Value { return _reference.value.value } } ================================================ FILE: Sources/Variable.swift ================================================ // // Variable.swift // GlueKit // // Created by Károly Lőrentey on 2015-11-30. // Copyright © 2015–2017 Károly Lőrentey. // /// A variable implements `UpdatableValueType` by having internal storage to a value. /// /// - SeeAlso: UnownedVariable, WeakVariable /// open class Variable: _BaseUpdatableValue { public typealias Change = ValueChange private var _value: Value /// Create a new variable with an initial value. public init(_ value: Value) { _value = value } override func rawGetValue() -> Value { return _value } override func rawSetValue(_ value: Value) { _value = value } } /// An unowned variable contains an unowned reference to an object that can be read and updated. Updates are observable. public class UnownedVariable: _BaseUpdatableValue { public typealias Change = ValueChange private unowned var _value: Value /// Create a new variable with an initial value. public init(_ value: Value) { _value = value } override func rawGetValue() -> Value { return _value } override func rawSetValue(_ value: Value) { _value = value } } /// A weak variable contains a weak reference to an object that can be read and updated. Updates are observable. public class WeakVariable: _BaseUpdatableValue { public typealias Value = Object? public typealias Change = ValueChange private weak var _value: Object? /// Create a new variable with a `nil` initial value. public override init() { _value = nil super.init() } /// Create a new variable with an initial value. public init(_ value: Value) { _value = value super.init() } override func rawGetValue() -> Value { return _value } override func rawSetValue(_ value: Value) { _value = value } } //MARK: Experimental subclasses for specific types // It would be so much more convenient if Swift allowed me to define these as extensions... public final class BoolVariable: Variable, ExpressibleByBooleanLiteral { public override init(_ value: Bool) { super.init(value) } public init(booleanLiteral value: BooleanLiteralType) { super.init(value) } } public final class IntVariable: Variable, ExpressibleByIntegerLiteral { public override init(_ value: Int) { super.init(value) } public init(integerLiteral value: IntegerLiteralType) { super.init(value) } } public final class FloatingPointVariable: Variable, ExpressibleByFloatLiteral where F: ExpressibleByFloatLiteral { public override init(_ value: F) { super.init(value) } public init(floatLiteral value: F.FloatLiteralType) { super.init(F(floatLiteral: value)) } } public typealias FloatVariable = FloatingPointVariable public typealias DoubleVariable = FloatingPointVariable public final class StringVariable: Variable, ExpressibleByStringLiteral { public override init(_ value: String) { super.init(value) } public init(unicodeScalarLiteral value: String.UnicodeScalarLiteralType) { super.init(value) } public init(extendedGraphemeClusterLiteral value: String.ExtendedGraphemeClusterLiteralType) { super.init(value) } public init(stringLiteral value: StringLiteralType) { super.init(value) } } public final class OptionalVariable: Variable>, ExpressibleByNilLiteral { public override init(_ value: Wrapped?) { super.init(value) } public init(nilLiteral: ()) { super.init(nil) } } ================================================ FILE: Tests/GlueKitTests/AnySinkTests.swift ================================================ // // AnySinkTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-25. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest import GlueKit private class TestSink: SinkType { typealias Value = Int func receive(_ value: Value) { // Noop } } class AnySinkTests: XCTestCase { func test_equality() { let sink1 = MockSink() let sink2 = MockSink() let sink3 = TestSink() let sink4 = TestSink() XCTAssertEqual(sink1, sink1) XCTAssertNotEqual(sink1, sink2) XCTAssertNotEqual(sink3, sink4) XCTAssertEqual(sink1.anySink, sink1.anySink) XCTAssertNotEqual(sink1.anySink, sink2.anySink) XCTAssertNotEqual(sink1.anySink, sink3.anySink) XCTAssertEqual(sink1.anySink, sink1.anySink.anySink) } func test_hashValue() { let sink = MockSink() XCTAssertEqual(sink.hashValue, sink.anySink.hashValue) } func test_receive() { let sink = MockSink() sink.expecting(1) { sink.receive(1) } sink.expecting(2) { sink.anySink.receive(2) } sink.expecting(3) { sink.anySink.anySink.receive(3) } } } ================================================ FILE: Tests/GlueKitTests/AnySourceTests.swift ================================================ // // AnySourceTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-25. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest import GlueKit private class ForwardingSource: SourceType { typealias Value = Source.Value let target: Source init(_ target: Source) { self.target = target } func add(_ sink: Sink) where Sink.Value == Value { target.add(sink) } func remove(_ sink: Sink) -> Sink where Sink.Value == Value { return target.remove(sink) } } class AnySourceTests: XCTestCase { func test_Works() { let signal = Signal() let source = signal.anySource let sink = MockSink() source.add(sink) sink.expecting(1) { signal.send(1) } source.remove(sink) sink.expectingNothing { signal.send(2) } } func test_Idempotent() { let signal = Signal() let source = signal.anySource.anySource let sink = MockSink() source.add(sink) sink.expecting(1) { signal.send(1) } source.remove(sink) sink.expectingNothing { signal.send(2) } } func test_Custom() { let signal = Signal() let source = ForwardingSource(signal).anySource let sink = MockSink() source.add(sink) sink.expecting(1) { signal.send(1) } source.remove(sink) sink.expectingNothing { signal.send(2) } } func test_RetainsOriginal() { weak var signal: Signal? = nil var source: AnySource? = nil do { let s = Signal() signal = s source = s.anySource withExtendedLifetime(s) {} } XCTAssertNotNil(signal) XCTAssertNotNil(source) source = nil XCTAssertNil(signal) } } ================================================ FILE: Tests/GlueKitTests/ArrayBufferingTests.swift ================================================ // // ArrayBufferingTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-11-02. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest @testable import GlueKit class ArrayBufferingTests: XCTestCase { func test_connectsImmediately() { let observable = TestObservableArray([1, 2, 3]) do { let buffered = observable.buffered() XCTAssertTrue(observable.isConnected) withExtendedLifetime(buffered) {} } XCTAssertFalse(observable.isConnected) } func test_properties() { let observable = TestObservableArray([1, 2, 3]) let buffered = observable.buffered() for b in [buffered, buffered.buffered()] { XCTAssertEqual(b.isBuffered, true) XCTAssertEqual(b[0], 1) XCTAssertEqual(b[1], 2) XCTAssertEqual(b[2], 3) XCTAssertEqual(b[0 ..< 2], [1, 2]) XCTAssertEqual(b.value, [1, 2, 3]) XCTAssertEqual(b.count, 3) } observable.apply(ArrayChange(initialCount: 3, modification: .replace(2, at: 1, with: 4))) XCTAssertEqual(buffered.value, [1, 4, 3]) } func test_updates() { let observable = TestObservableArray([1, 2, 3]) let buffered = observable.buffered() let sink = MockArrayObserver(buffered) sink.expecting(["begin", "3.remove(3, at: 2)", "end"]) { observable.apply(ArrayChange(initialCount: 3, modification: .remove(3, at: 2))) } XCTAssertEqual(buffered.value, [1, 2]) sink.expecting("begin") { observable.beginTransaction() } sink.expectingNothing { observable.apply(ArrayChange(initialCount: 2, modification: .replace(1, at: 0, with: 2))) } XCTAssertEqual(buffered.value, [1, 2]) sink.expectingNothing { observable.apply(ArrayChange(initialCount: 2, modification: .insert(6, at: 2))) } XCTAssertEqual(buffered.value, [1, 2]) sink.expecting(["2.replace(1, at: 0, with: 2).insert(6, at: 2)", "end"]) { observable.endTransaction() } XCTAssertEqual(buffered.value, [2, 2, 6]) sink.disconnect() } } ================================================ FILE: Tests/GlueKitTests/ArrayChangeSeparationTests.swift ================================================ // // ArrayChangeSeparationTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-07. // Copyright © 2015–2017 Károly Lőrentey. // import Foundation import XCTest import GlueKit /// Generate an exhaustive list of array changes, each consisting of a sequence of at most /// `depth` range replacements that insert at most `maxInsertion` elements. func generateArrayChanges(input: [Int], depth: Int, maxInsertion: Int, body: @escaping (ArrayChange) -> Void) { func insertionsAtLevel(_ level: Int) -> [[Int]] { // Returns an array of [], [30], [30, 31], ..., up to maxInsertionLength let s = 10 * level return (0...maxInsertion).map { (0..<$0).map { s + $0 } } } func recurse(level: Int, buffer: [Int], prefix: ArrayChange) { if level >= depth { return } for startIndex in buffer.startIndex...buffer.endIndex { for endIndex in startIndex...buffer.endIndex { let range = startIndex ..< endIndex if range.count > 0 { // Move a slice let slice = Array(buffer[range]) var b = buffer b.removeSubrange(range) for target in 0 ... buffer.count - range.count { if target == startIndex { continue } var next = b next.insert(contentsOf: slice, at: target) var change = prefix change.add(ArrayModification.replaceSlice(slice, at: startIndex, with: [])) change.add(ArrayModification.replaceSlice([], at: target, with: slice)) body(change) recurse(level: level + 1, buffer: next, prefix: change) } } for insertion in insertionsAtLevel(level) { if insertion.count == 0 && endIndex == startIndex { // Skip replacing empty with empty continue } var next = buffer next.replaceSubrange(range, with: insertion) let mod = ArrayModification.replaceSlice(Array(buffer[range]), at: startIndex, with: insertion) let change = prefix.merged(with: ArrayChange(initialCount: buffer.count, modification: mod)) body(change) recurse(level: level + 1, buffer: next, prefix: change) } } } } recurse(level: 0, buffer: input, prefix: ArrayChange(initialCount: input.count)) } class ArrayChangeSeparationTests: XCTestCase { func testSimpleSeparation() { let input = [-1, -2] generateArrayChanges(input: input, depth: 3, maxInsertion: 2) { change in // We'll emulate UITableView's content update logic, feed it the change and see if we // arrive at a result that matches the updated array. var output = input output.apply(change) var table = input for index in change.deletedIndices.reversed() { table.remove(at: index) } for index in change.insertedIndices { table.insert(output[index], at: index) } XCTAssertEqual(table, output) } } func testSeparation() { let input = [-1, -2, -3] generateArrayChanges(input: input, depth: 2, maxInsertion: 2) { change in // We'll emulate UITableView's content update logic, feed it the change and see if we // arrive at a result that matches the updated array. var output = input output.apply(change) let sep = change.separated() var table = input var deleted = sep.deleted var inserted = sep.inserted var moveTargets = IndexSet() var movedElements: [Int] = [] for (old, new) in sep.moved.sorted(by: { $0.1 < $1.1 }) { moveTargets.insert(new) movedElements.append(table[old]) deleted.insert(old) } inserted.formUnion(moveTargets) for index in deleted.reversed() { table.remove(at: index) } for index in inserted { if moveTargets.contains(index) { table.insert(movedElements.removeFirst(), at: index) } else { table.insert(output[index], at: index) } } XCTAssertEqual(table, output) } } } ================================================ FILE: Tests/GlueKitTests/ArrayChangeTests.swift ================================================ // // ArrayChangeTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-07. // Copyright © 2015–2017 Károly Lőrentey. // import Foundation import XCTest import GlueKit class ArrayChangeTests: XCTestCase { func testCounts() { var change = ArrayChange(initialCount: 10) change.add(.insert("foo", at: 2)) change.add(.replace("foo", at: 4, with: "bar")) change.add(.remove("foo", at: 0)) change.add(.replaceSlice(["foo", "bar"], at: 6, with: ["1", "2", "3"])) XCTAssertEqual(change.initialCount, 10) XCTAssertEqual(change.finalCount, 11) XCTAssertEqual(change.deltaCount, 1) XCTAssertTrue(change.countChange == ValueChange(from: 10, to: 11)) } func testExerciseMerging() { // Exhaustively test the merging of all variations of modification sequences. let startSequence = [0, 1] let maxLevels = 4 let maxInsertionLength = 2 func insertionsAtLevel(_ level: Int) -> [[Int]] { // Returns an array of [], [30], [30, 31], ..., up to maxInsertionLength let s = 10 * level return (0...maxInsertionLength).map { (0..<$0).map { s + $0 } } } func printTrace(_ input: [Int], change: ArrayChange, output: [Int], trace: [ArrayModification], applied: [Int]) { print("Given this input:") print(" \(input)") print("This sequence of changes:") for t in trace { print(" Replace \(t.oldElements) at \(t.startIndex) with \(t.newElements)") } print("Was collapsed to:") for m in change.modifications { print(" Replace \(m.oldElements) at \(m.startIndex) with \(m.newElements)") } print("Which resulted in:") print(" \(applied)") print("Instead of:") print(" \(output)") } func recurse(_ level: Int, input: [Int], change: ArrayChange, output: [Int], trace: [ArrayModification]) { var applied = input XCTAssertEqual(applied.count, change.initialCount) for m in change.modifications { if applied.count < m.inputRange.upperBound { printTrace(input, change: change, output: output, trace: trace, applied: applied) print() } if Array(applied[m.inputRange]) != m.oldElements { printTrace(input, change: change, output: output, trace: trace, applied: applied) XCTAssertEqual(Array(applied[m.inputRange]), m.oldElements) } applied.apply(m) } XCTAssertEqual(applied.count, change.finalCount) if applied != output { XCTAssertEqual(applied, output) printTrace(input, change: change, output: output, trace: trace, applied: applied) return } // Also do a quick test for reversing the change. var undo = applied undo.apply(change.reversed()) XCTAssertEqual(undo, input) if level < maxLevels { for startIndex in output.startIndex...output.endIndex { for endIndex in startIndex...output.endIndex { for insertion in insertionsAtLevel(level) { if insertion.count == 0 && endIndex == startIndex { // Skip replacing empty with empty continue } var nextOutput = output nextOutput.replaceSubrange(startIndex..(initialCount: startSequence.count) recurse(1, input: startSequence, change: startChange, output: startSequence, trace: []) } func testMap() { let c1 = ArrayChange(initialCount: 10, modification: .insert(1, at: 3)) .merged(with: ArrayChange(initialCount: 11, modification: .replace(11, at: 1, with: 2))) .merged(with: ArrayChange(initialCount: 11, modification: .remove(13, at: 4))) .merged(with: ArrayChange(initialCount: 10, modification: .replaceSlice([18, 19], at: 8, with: [5, 6]))) let c2 = ArrayChange(initialCount: 10, modification: .insert("1", at: 3)) .merged(with: ArrayChange(initialCount: 11, modification: .replace("11", at: 1, with: "2"))) .merged(with: ArrayChange(initialCount: 11, modification: .remove("13", at: 4))) .merged(with: ArrayChange(initialCount: 10, modification: .replaceSlice(["18", "19"], at: 8, with: ["5", "6"]))) let m = c1.map { "\($0)" } XCTAssertEqual(m.initialCount, c2.initialCount) XCTAssertEqual(m.deltaCount, c2.deltaCount) XCTAssert(m.modifications.elementsEqual(c2.modifications, by: ==)) } func testApply() { var array = [0, 1, 2, 3, 4] var change = ArrayChange(initialCount: 5) change.add(.insert(10, at: 2)) change.add(.remove(1, at: 1)) change.add(.replace(4, at: 4, with: 20)) change.apply(on: &array) XCTAssertEqual(array, [0, 10, 2, 3, 20]) } func testDescription() { var change = ArrayChange(initialCount: 5) change.add(.insert(10, at: 2)) change.add(.remove(1, at: 1)) change.add(.replace(4, at: 4, with: 20)) XCTAssertEqual(change.description, "ArrayChange initialCount: 5, 2 modifications") XCTAssertEqual(change.debugDescription, "GlueKit.ArrayChange initialCount: 5, 2 modifications") } func testRemovingEqualChanges() { var change = ArrayChange(initialCount: 5) change.add(.remove(1, at: 1)) change.add(.insert(1, at: 1)) change.add(.replace(4, at: 4, with: 40)) XCTAssertEqual(change.modifications, [.replace(1, at: 1, with: 1), .replace(4, at: 4, with: 40)]) XCTAssertEqual(change.removingEqualChanges().modifications, [.replace(4, at: 4, with: 40)]) } } ================================================ FILE: Tests/GlueKitTests/ArrayConcatenationTests.swift ================================================ // // ArrayConcatenationTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-10. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest import GlueKit class ArrayConcatenationTests: XCTestCase { func testConcatenation() { func check(a: [Int], b: [Int], c: O, file: StaticString = #file, line: UInt = #line) where O.Element == Int { XCTAssertEqual(c.count, a.count + b.count, file: file, line: line) let v = a + b XCTAssertEqual(c.value, v, file: file, line: line) for i in 0 ..< v.count { XCTAssertEqual(c[i], v[i], file: file, line: line) for j in i ..< v.count { XCTAssertEqual(c[i ..< j], v[i ..< j], file: file, line: line) } } } let a: ArrayVariable = [0, 1, 2] let b: ArrayVariable = [10, 20] let c = a + b XCTAssertFalse(c.isBuffered) XCTAssertEqual(c.count, 5) XCTAssertEqual(c.value, [0, 1, 2, 10, 20]) XCTAssertEqual(c[0], 0) XCTAssertEqual(c[1], 1) XCTAssertEqual(c[2], 2) XCTAssertEqual(c[3], 10) XCTAssertEqual(c[4], 20) check(a: a.value, b: b.value, c: c) let mock = MockArrayObserver(c) mock.expecting(["begin", "5.insert(30, at: 5)", "end"]) { b.append(30) } check(a: a.value, b: b.value, c: c) mock.expecting(["begin", "6.insert(3, at: 3)", "end"]) { a.append(3) } check(a: a.value, b: b.value, c: c) } } ================================================ FILE: Tests/GlueKitTests/ArrayFilteringTests.swift ================================================ // // ArrayFilteringTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-09-27. // Copyright © 2015–2017 Károly Lőrentey. // import Foundation import XCTest import GlueKit private class Book: Equatable, CustomStringConvertible { let id: String let title: Variable init(id: String, title: String) { self.id = id self.title = Variable(title) } var description: String { return id } static func ==(a: Book, b: Book) -> Bool { return a.title.value == b.title.value } } class ArrayFilteringTests: XCTestCase { func test_filterOnPredicate_getters() { let array: ArrayVariable = [1, 3, 5, 6] let even = array.filter { $0 % 2 == 0 } XCTAssertFalse(even.isBuffered) XCTAssertEqual(even.count, 1) XCTAssertEqual(even[0], 6) XCTAssertEqual(even[0 ..< 1], ArraySlice([6])) XCTAssertEqual(even.value, [6]) array.value = Array(0 ..< 10) XCTAssertEqual(even.count, 5) XCTAssertEqual(even.value, [0, 2, 4, 6, 8]) array.remove(at: 3) XCTAssertEqual(even.count, 5) XCTAssertEqual(even.value, [0, 2, 4, 6, 8]) array.remove(at: 3) XCTAssertEqual(even.count, 4) XCTAssertEqual(even.value, [0, 2, 6, 8]) array.insert(10, at: 2) XCTAssertEqual(even.count, 5) XCTAssertEqual(even.value, [0, 10, 2, 6, 8]) array[2] = 12 XCTAssertEqual(even.count, 5) XCTAssertEqual(even.value, [0, 12, 2, 6, 8]) array[2] = 11 XCTAssertEqual(even.count, 4) XCTAssertEqual(even.value, [0, 2, 6, 8]) array[2] = 9 XCTAssertEqual(even.count, 4) XCTAssertEqual(even.value, [0, 2, 6, 8]) array[2] = 10 XCTAssertEqual(even.count, 5) XCTAssertEqual(even.value, [0, 10, 2, 6, 8]) array.removeAll() XCTAssertEqual(even.count, 0) XCTAssertEqual(even.value, []) } func test_filterOnPredicate_updates() { let array: ArrayVariable = [0, 1, 2, 3, 4] let evenMembers = array.filter { $0 % 2 == 0 } let mock = MockArrayObserver(evenMembers) mock.expecting(["begin", "3.insert(6, at: 3)", "end"]) { array.insert(contentsOf: [5, 6, 7], at: 5) } mock.expecting(["begin", "4.replaceSlice([2, 4], at: 1, with: [])", "end"]) { array.removeSubrange(1 ..< 5) } } func test_filterOnObservableBool_getters() { let b1 = Book(id: "b1", title: "Winnie the Pooh") let b2 = Book(id: "b2", title: "The Color of Magic") let b3 = Book(id: "b3", title: "Structure and Interpretation of Computer Programs") let b4 = Book(id: "b4", title: "Numerical Recipes in C++") let array: ArrayVariable = [b1, b2, b3, b4] // Books with "of" in their title. let filtered = array.filter { $0.title.map { $0.lowercased().contains("of") } } XCTAssertEqual(filtered.isBuffered, false) XCTAssertEqual(filtered.count, 2) XCTAssertEqual(filtered[0], b2) XCTAssertEqual(filtered[1], b3) XCTAssertEqual(filtered[0 ..< 2], ArraySlice([b2, b3])) XCTAssertEqual(filtered.value, [b2, b3]) let b5 = Book(id: "b5", title: "Of Mice and Men") array.append(b5) XCTAssertEqual(filtered.count, 3) XCTAssertEqual(filtered.value, [b2, b3, b5]) array.remove(at: 1) XCTAssertEqual(filtered.count, 2) XCTAssertEqual(filtered.value, [b3, b5]) b4.title.value = "The TeXbook" XCTAssertEqual(filtered.count, 2) XCTAssertEqual(filtered.value, [b3, b5]) b4.title.value = "House of Leaves" XCTAssertEqual(filtered.count, 3) XCTAssertEqual(filtered.value, [b3, b4, b5]) b4.title.value = "Good Omens" XCTAssertEqual(filtered.count, 2) XCTAssertEqual(filtered.value, [b3, b5]) } func test_complex_updates() { let b1 = Book(id: "b1", title: "Winnie the Pooh") let b2 = Book(id: "b2", title: "The Color of Magic") let b3 = Book(id: "b3", title: "Structure and Interpretation of Computer Programs") let b4 = Book(id: "b4", title: "Numerical Recipes in C++") let array: ArrayVariable = [b1, b2, b3, b4] // Books with "of" in their title. let filtered = array.filter { $0.title.map { $0.lowercased().contains("of") } } let mock = MockArrayObserver(filtered) // filtered is [b2, b3] let b5 = Book(id: "b5", title: "Of Mice and Men") mock.expecting(["begin", "2.insert(b5, at: 2)", "end"]) { array.append(b5) } // filtered is [b2, b3, b5] mock.expecting(["begin", "3.remove(b2, at: 0)", "end"]) { _ = array.remove(at: 1) } // filtered is [b3, b5] mock.expecting(["begin", "end"]) { b4.title.value = "The TeXbook" } // filtered is [b3, b5] mock.expecting(["begin", "2.insert(b4, at: 1)", "end"]) { b4.title.value = "House of Leaves" } // filtered is [b3, b4, b5] mock.expecting(["begin", "3.remove(b4, at: 1)", "end"]) { b4.title.value = "Good Omens" } // filtered is [b3, b5] } } ================================================ FILE: Tests/GlueKitTests/ArrayFoldingTests.swift ================================================ // // ArrayFoldingTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-09. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest import GlueKit class ArrayFoldingTests: XCTestCase { func testSum() { let array = ArrayVariable([1, 2, 3]) let sum = array.sum() XCTAssertEqual(sum.value, 6) array.append(4) XCTAssertEqual(sum.value, 10) array.insert(5, at: 1) XCTAssertEqual(sum.value, 15) array.remove(at: 0) XCTAssertEqual(sum.value, 14) array.removeAll() XCTAssertEqual(sum.value, 0) } } ================================================ FILE: Tests/GlueKitTests/ArrayMappingTests.swift ================================================ // // SelectOperatorTests.swift // GlueKit // // Created by Károly Lőrentey on 2015-12-06. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest import GlueKit private class Book { let title: StringVariable let authors: ArrayVariable init(_ title: String, _ authors: [String] = []) { self.title = .init(title) self.authors = .init(authors) } } class ArrayMappingTests: XCTestCase { func test_map_value() { let b1 = Book("foo") let b2 = Book("bar") let b3 = Book("baz") let books: ArrayVariable = [b1, b2, b3] // Ignoring observability like this isn't a good idea; if we change the title of a book, the titles // array won't get updated. However, it simplifies testing that we don't need to set up a read-only // property in our fixture. let titles = books.map{ $0.title.value } XCTAssertFalse(titles.isBuffered) XCTAssertEqual(titles.count, 3) XCTAssertEqual(titles.observableCount.value, 3) XCTAssertEqual(titles[0], "foo") XCTAssertEqual(titles[1 ..< 3], ArraySlice(["bar", "baz"])) XCTAssertEqual(titles.value, ["foo", "bar", "baz"]) let mock = MockArrayObserver(titles) mock.expecting(["begin", "3.insert(fred, at: 3)", "end"]) { books.append(Book("fred")) } XCTAssertEqual(titles.value, ["foo", "bar", "baz", "fred"]) mock.expecting(["begin", "4.remove(bar, at: 1)", "end"]) { _ = books.remove(at: 1) } XCTAssertEqual(titles.value, ["foo", "baz", "fred"]) mock.expecting(["begin", "3.replace(foo, at: 0, with: fuzzy)", "end"]) { _ = books[0] = Book("fuzzy") } XCTAssertEqual(titles.value, ["fuzzy", "baz", "fred"]) let barney = Book("barney") mock.expecting(["begin", "3.replaceSlice([baz, fred], at: 1, with: [barney])", "end"]) { _ = books.replaceSubrange(1 ..< 3, with: [barney]) } XCTAssertEqual(titles.value, ["fuzzy", "barney"]) // The observable doesn't know the title of a book may change, so it won't notice when we modify it. mock.expectingNothing { barney.title.value = "bazaar" } // However, this particular observable generates results on the fly, so the pull-based API includes the change. XCTAssertEqual(titles.value, ["fuzzy", "bazaar"]) } func test_bufferedMap_observed() { let b1 = Book("foo") let b2 = Book("bar") let b3 = Book("baz") let books: ArrayVariable = [b1, b2, b3] // Ignoring observability like this isn't a good idea; if we change the title of a book, the titles // array won't get updated. However, it simplifies testing that we don't need to set up a read-only // property in our fixture. let titles = books.bufferedMap{ $0.title.value } XCTAssertTrue(titles.isBuffered) XCTAssertEqual(titles.count, 3) XCTAssertEqual(titles.observableCount.value, 3) XCTAssertEqual(titles[0], "foo") XCTAssertEqual(titles[1 ..< 3], ArraySlice(["bar", "baz"])) XCTAssertEqual(titles.value, ["foo", "bar", "baz"]) let mock = MockArrayObserver(titles) mock.expecting(["begin", "3.insert(fred, at: 3)", "end"]) { books.append(Book("fred")) } XCTAssertEqual(titles.value, ["foo", "bar", "baz", "fred"]) mock.expecting(["begin", "4.remove(bar, at: 1)", "end"]) { _ = books.remove(at: 1) } XCTAssertEqual(titles.value, ["foo", "baz", "fred"]) mock.expecting(["begin", "3.replace(foo, at: 0, with: fuzzy)", "end"]) { _ = books[0] = Book("fuzzy") } XCTAssertEqual(titles.value, ["fuzzy", "baz", "fred"]) let barney = Book("barney") mock.expecting(["begin", "3.replaceSlice([baz, fred], at: 1, with: [barney])", "end"]) { _ = books.replaceSubrange(1 ..< 3, with: [barney]) } XCTAssertEqual(titles.value, ["fuzzy", "barney"]) // The observable doesn't know the title of a book may change, so it won't notice when we modify it. mock.expectingNothing { barney.title.value = "bazaar" } // The observable is buffered, so if we pull a value out of it, it won't include the update. XCTAssertEqual(titles.value, ["fuzzy", "barney"]) } func test_bufferedMap_unobserved() { let b1 = Book("foo") let b2 = Book("bar") let b3 = Book("baz") let books: ArrayVariable = [b1, b2, b3] let titles = books.bufferedMap{ $0.title.value } // If the buffered map is not observed, it runs a differed code path, so test that as well. books.append(Book("fred")) XCTAssertEqual(titles.value, ["foo", "bar", "baz", "fred"]) _ = books.remove(at: 1) XCTAssertEqual(titles.value, ["foo", "baz", "fred"]) _ = books[0] = Book("fuzzy") XCTAssertEqual(titles.value, ["fuzzy", "baz", "fred"]) let barney = Book("barney") _ = books.replaceSubrange(1 ..< 3, with: [barney]) XCTAssertEqual(titles.value, ["fuzzy", "barney"]) // The observable doesn't know the title of a book may change, so it won't notice when we modify it. barney.title.value = "bazaar" // The observable is buffered, so if we pull a value out of it, it won't include the update. XCTAssertEqual(titles.value, ["fuzzy", "barney"]) } func test_map_valueField() { let b1 = Book("foo") let b2 = Book("bar") let b3 = Book("baz") let books: ArrayVariable = [b1, b2, b3] let titles = books.map{$0.title} XCTAssertFalse(titles.isBuffered) XCTAssertEqual(titles.count, 3) XCTAssertEqual(titles[0], "foo") XCTAssertEqual(titles[1 ..< 3], ArraySlice(["bar", "baz"])) XCTAssertEqual(titles.value, ["foo", "bar", "baz"]) let mock = MockArrayObserver(titles) let b4 = Book("fred") mock.expecting(["begin", "3.insert(fred, at: 3)", "end"]) { books.append(b4) } XCTAssertEqual(titles.value, ["foo", "bar", "baz", "fred"]) mock.expecting(["begin", "4.remove(bar, at: 1)", "end"]) { _ = books.remove(at: 1) } XCTAssertEqual(titles.value, ["foo", "baz", "fred"]) mock.expecting(["begin", "3.replace(baz, at: 1, with: bazaar)", "end"]) { b3.title.value = "bazaar" } XCTAssertEqual(titles.value, ["foo", "bazaar", "fred"]) } func test_flatMap_arrayField() { let b1 = Book("foo", ["a", "b", "c"]) let b2 = Book("bar", ["b", "d"]) let b3 = Book("baz", ["a"]) let b4 = Book("zoo", []) let books: ArrayVariable = [b1, b2, b3, b4] let authors = books.flatMap{$0.authors} XCTAssertEqual(authors.isBuffered, false) XCTAssertEqual(authors.value, [ /*b1*/ "a", "b", "c", /*b2*/ "b", "d", /*b3*/ "a", /*b4*/ ]) XCTAssertEqual(authors.count, 6) XCTAssertEqual(authors[0], "a") XCTAssertEqual(authors[4], "d") XCTAssertEqual(authors[2..<4], ArraySlice(["c", "b"])) func checkSlices(file: StaticString = #file, line: UInt = #line) { let value = authors.value for i in 0 ..< authors.count { for j in i ..< authors.count { XCTAssertEqual(authors[i ..< j], value[i ..< j], file: file, line: line) } } } checkSlices() let mock = MockArrayObserver(authors) let b5 = Book("fred", ["e"]) mock.expecting(["begin", "6.insert(e, at: 6)", "end"]) { books.append(b5) } XCTAssertEqual(authors.value, [ /*b1*/ "a", "b", "c", /*b2*/ "b", "d", /*b3*/ "a", /*b4*/ /*b5*/ "e" ]) checkSlices() mock.expecting(["begin", "7.replaceSlice([b, d], at: 3, with: [])", "end"]) { _ = books.remove(at: 1) // b2 } XCTAssertEqual(authors.value, [ /*b1*/ "a", "b", "c", /*b3*/ "a", /*b4*/ /*b5*/ "e" ]) checkSlices() mock.expecting(["begin", "5.replaceSlice([], at: 0, with: [b, d])", "end"]) { books.insert(b2, at: 0) } XCTAssertEqual(authors.value, [ /*b2*/ "b", "d", /*b1*/ "a", "b", "c", /*b3*/ "a", /*b4*/ /*b5*/ "e" ]) checkSlices() mock.expecting(["begin", "7.insert(*, at: 1)", "end"]) { b2.authors.insert("*", at: 1) } XCTAssertEqual(authors.value, [ /*b2*/ "b", "*", "d", /*b1*/ "a", "b", "c", /*b3*/ "a", /*b4*/ /*b5*/ "e" ]) checkSlices() mock.expecting(["begin", "8.replace(*, at: 1, with: f)", "end"]) { b2.authors[1] = "f" } XCTAssertEqual(authors.value, [ /*b2*/ "b", "f", "d", /*b1*/ "a", "b", "c", /*b3*/ "a", /*b4*/ /*b5*/ "e" ]) checkSlices() mock.expecting(["begin", "8.insert(g, at: 8)", "end"]) { b5.authors.append("g") } XCTAssertEqual(authors.value, [ /*b2*/ "b", "f", "d", /*b1*/ "a", "b", "c", /*b3*/ "a", /*b4*/ /*b5*/ "e", "g" ]) checkSlices() // Remove all authors from each book, one by one. mock.expecting(["begin", "9.remove(a, at: 6)", "end"]) { b3.authors.value = [] } XCTAssertEqual(authors.value, [ /*b2*/ "b", "f", "d", /*b1*/ "a", "b", "c", /*b3*/ /*b4*/ /*b5*/ "e", "g" ]) checkSlices() mock.expecting(["begin", "8.replaceSlice([a, b, c], at: 3, with: [])", "end"]) { b1.authors.value = [] } XCTAssertEqual(authors.value, [ /*b2*/ "b", "f", "d", /*b1*/ /*b3*/ /*b4*/ /*b5*/ "e", "g" ]) checkSlices() mock.expecting(["begin", "5.replaceSlice([], at: 5, with: [b, f, d, e, g])", "end"]) { books.append(contentsOf: books.value) } XCTAssertEqual(authors.value, [ /*b2*/ "b", "f", "d", /*b1*/ /*b3*/ /*b4*/ /*b5*/ "e", "g", /*b2*/ "b", "f", "d", /*b1*/ /*b3*/ /*b4*/ /*b5*/ "e", "g" ]) checkSlices() mock.expectingOneOf([ ["begin", "10.replaceSlice([b, f, d], at: 0, with: [])", "7.replaceSlice([b, f, d], at: 2, with: [])", "end"], ["begin", "10.replaceSlice([b, f, d], at: 5, with: [])", "7.replaceSlice([b, f, d], at: 0, with: [])", "end"] ]) { b2.authors.value = [] } XCTAssertEqual(authors.value, [ /*b2*/ /*b1*/ /*b3*/ /*b4*/ /*b5*/ "e", "g", /*b2*/ /*b1*/ /*b3*/ /*b4*/ /*b5*/ "e", "g" ]) checkSlices() mock.expecting(["begin", "4.replaceSlice([e, g], at: 2, with: [])", "end"]) { books.removeSubrange(5 ..< 10) } XCTAssertEqual(authors.value, [ /*b2*/ /*b1*/ /*b3*/ /*b4*/ /*b5*/ "e", "g" ]) checkSlices() mock.expecting(["begin", "2.replaceSlice([e, g], at: 0, with: [])", "end"]) { b5.authors.value = [] } XCTAssertEqual(authors.value, [ // b2 b1 b3 b4 b5 ]) checkSlices() // At this point, no book has any author. mock.expecting(["begin", "end"]) { books.append(contentsOf: books.value) } XCTAssertEqual(authors.value, [ // b2 b1 b3 b4 b5 b2 b1 b3 b4 b5 ]) checkSlices() mock.expectingOneOf([ ["begin", "0.replaceSlice([], at: 0, with: [3a, 3b])", "2.replaceSlice([], at: 0, with: [3a, 3b])", "end"], ["begin", "0.replaceSlice([], at: 0, with: [3a, 3b])", "2.replaceSlice([], at: 2, with: [3a, 3b])", "end"], ]) { b3.authors.value = ["3a", "3b"] } XCTAssertEqual(authors.value, [ /*b2*/ /*b1*/ /*b3*/ "3a", "3b", /*b4*/ /*b5*/ /*b2*/ /*b1*/ /*b3*/ "3a", "3b", /*b4*/ /*b5*/ ]) checkSlices() mock.expectingOneOf([ ["begin", "4.insert(1, at: 0)", "5.insert(1, at: 3)", "end"], ["begin", "4.insert(1, at: 2)", "5.insert(1, at: 0)", "end"], ]) { b1.authors.value = ["1"] } XCTAssertEqual(authors.value, [ /*b2*/ /*b1*/ "1", /*b3*/ "3a", "3b", /*b4*/ /*b5*/ /*b2*/ /*b1*/ "1", /*b3*/ "3a", "3b", /*b4*/ /*b5*/ ]) checkSlices() mock.expecting(["begin", "6.replaceSlice([3a, 3b], at: 4, with: [])", "end"]) { books.removeSubrange(7 ..< 10) } XCTAssertEqual(authors.value, [ /*b2*/ /*b1*/ "1", /*b3*/ "3a", "3b", /*b4*/ /*b5*/ /*b2*/ /*b1*/ "1", ]) checkSlices() mock.expecting(["begin", "4.insert(5a, at: 3)", "end"]) { b5.authors.append("5a") } XCTAssertEqual(authors.value, [ /*b2*/ /*b1*/ "1", /*b3*/ "3a", "3b", /*b4*/ /*b5*/ "5a", /*b2*/ /*b1*/ "1", ]) checkSlices() mock.expecting(["begin", "5.insert(5b, at: 4)", "end"]) { b5.authors.append("5b") } XCTAssertEqual(authors.value, [ /*b2*/ /*b1*/ "1", /*b3*/ "3a", "3b", /*b4*/ /*b5*/ "5a", "5b", /*b2*/ /*b1*/ "1", ]) checkSlices() mock.expectingOneOf([ ["begin", "6.insert(2, at: 0)", "7.insert(2, at: 6)", "end"], ["begin", "6.insert(2, at: 5)", "7.insert(2, at: 0)", "end"], ]) { b2.authors.append("2") } XCTAssertEqual(authors.value, [ /*b2*/ "2", /*b1*/ "1", /*b3*/ "3a", "3b", /*b4*/ /*b5*/ "5a", "5b", /*b2*/ "2", /*b1*/ "1", ]) checkSlices() mock.expecting(["begin", "8.remove(2, at: 6)", "end"]) { _ = books.remove(at: 5) // b2 } XCTAssertEqual(authors.value, [ /*b2*/ "2", /*b1*/ "1", /*b3*/ "3a", "3b", /*b4*/ /*b5*/ "5a", "5b", /*b1*/ "1", ]) checkSlices() mock.expecting(["begin", "7.remove(1, at: 1)", "end"]) { _ = books.remove(at: 1) // b1 } XCTAssertEqual(authors.value, [ /*b2*/ "2", /*b3*/ "3a", "3b", /*b4*/ /*b5*/ "5a", "5b", /*b1*/ "1", ]) checkSlices() mock.expecting(["begin", "6.replaceSlice([5a, 5b], at: 3, with: [])", "end"]) { b5.authors.value = [] } XCTAssertEqual(authors.value, [ /*b2*/ "2", /*b3*/ "3a", "3b", /*b4*/ /*b5*/ /*b1*/ "1", ]) checkSlices() mock.expecting(["begin", "4.remove(1, at: 3)", "end"]) { _ = books.removeLast() // b1 } XCTAssertEqual(authors.value, [ /*b2*/ "2", /*b3*/ "3a", "3b", /*b4*/ /*b5*/ ]) checkSlices() mock.expecting(["begin", "end"]) { _ = books.removeLast() // b5 } XCTAssertEqual(authors.value, [ /*b2*/ "2", /*b3*/ "3a", "3b", /*b4*/ ]) checkSlices() mock.expecting(["begin", "3.remove(2, at: 0)", "end"]) { b2.authors.value = [] } XCTAssertEqual(authors.value, [ /*b2*/ /*b3*/ "3a", "3b", /*b4*/ ]) checkSlices() mock.expecting(["begin", "end"]) { _ = books.removeFirst() // b2 } XCTAssertEqual(authors.value, [ /*b3*/ "3a", "3b", /*b4*/ ]) checkSlices() mock.expecting(["begin", "end"]) { _ = books.removeLast() // b4 } XCTAssertEqual(authors.value, [ /*b3*/ "3a", "3b", ]) checkSlices() mock.expecting(["begin", "2.replaceSlice([3a, 3b], at: 0, with: [])", "end"]) { _ = books.removeLast() // b3 } XCTAssertEqual(authors.value, []) checkSlices() } } ================================================ FILE: Tests/GlueKitTests/ArrayModificationTests.swift ================================================ // // ArrayModificationTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-07. // Copyright © 2015–2017 Károly Lőrentey. // import Foundation import XCTest @testable import GlueKit private func ==(a: ArrayModificationMergeResult, b: ArrayModificationMergeResult) -> Bool { switch a { case .disjunctOrderedAfter: if case .disjunctOrderedAfter = b { return true } return false case .disjunctOrderedBefore: if case .disjunctOrderedBefore = b { return true } return false case .collapsedToNoChange: if case .collapsedToNoChange = b { return true } return false case .collapsedTo(let ae): if case .collapsedTo(let be) = b { return ae == be } return false } } func ==(a: [ArrayModification], b: [ArrayModification]) -> Bool { return a.elementsEqual(b, by: ==) } func !=(a: [ArrayModification], b: [ArrayModification]) -> Bool { return !(a == b) } func XCTAssertEqual(_ a: [ArrayModification], _ b: [ArrayModification], file: StaticString = #file, line: UInt = #line) { if a != b { XCTFail("\(a) is not equal to \(b)", file: file, line: line) } } class ArrayModificationTests: XCTestCase { func testInsertion() { var a = [1, 2, 3] let mod = ArrayModification.insert(10, at: 2) XCTAssertEqual(mod.deltaCount, 1) XCTAssertEqual(mod.startIndex, 2) XCTAssertEqual(mod.inputRange, 2..<2) XCTAssertEqual(mod.outputRange, 2..<3) XCTAssertEqual(mod.oldElements, []) XCTAssertEqual(mod.newElements, [10]) a.apply(mod) XCTAssertEqual(a, [1, 2, 10, 3]) XCTAssert(mod.reversed == .remove(10, at: 2)) XCTAssert(mod.map { "\($0)" } == .insert("10", at: 2)) } func testRemoval() { var a = [1, 2, 3] let mod = ArrayModification.remove(2, at: 1) XCTAssertEqual(mod.deltaCount, -1) XCTAssertEqual(mod.startIndex, 1) XCTAssertEqual(mod.inputRange, 1..<2) XCTAssertEqual(mod.outputRange, 1..<1) XCTAssertEqual(mod.oldElements, [2]) XCTAssertEqual(mod.newElements, []) a.apply(mod) XCTAssertEqual(a, [1, 3]) XCTAssert(mod.reversed == .insert(2, at: 1)) XCTAssert(mod.map { "\($0)" } == .remove("2", at: 1)) } func testReplacement() { var a = [1, 2, 3] let mod = ArrayModification.replace(2, at: 1, with: 10) XCTAssertEqual(mod.deltaCount, 0) XCTAssertEqual(mod.startIndex, 1) XCTAssertEqual(mod.inputRange, 1..<2) XCTAssertEqual(mod.outputRange, 1..<2) XCTAssertEqual(mod.oldElements, [2]) XCTAssertEqual(mod.newElements, [10]) a.apply(mod) XCTAssertEqual(a, [1, 10, 3]) XCTAssert(mod.reversed == .replace(10, at: 1, with: 2)) XCTAssert(mod.map { "\($0)" } == .replace("2", at: 1, with: "10")) } func testRangeReplacement() { var a = [1, 2, 3] let mod = ArrayModification.replaceSlice([2], at: 1, with: [10, 20]) XCTAssertEqual(mod.deltaCount, 1) XCTAssertEqual(mod.startIndex, 1) XCTAssertEqual(mod.inputRange, 1..<2) XCTAssertEqual(mod.outputRange, 1..<3) XCTAssertEqual(mod.oldElements, [2]) XCTAssertEqual(mod.newElements, [10, 20]) a.apply(mod) XCTAssertEqual(a, [1, 10, 20, 3]) XCTAssert(mod.reversed == .replaceSlice([10, 20], at: 1, with: [2])) XCTAssert(mod.map { "\($0)" } == ArrayModification.replaceSlice(["2"], at: 1, with: ["10", "20"])) } func testMergeIntoEmpty() { let first = ArrayModification.replaceSlice([], at: 10, with: ["a", "b", "c"]) let m1 = ArrayModification.replaceSlice(["a", "b", "c"], at: 10, with: []) XCTAssert(first.merged(with: m1) == .collapsedToNoChange) } func testMergeIntoNonEmpty() { func x(_ n: Int) -> [String] { return Array(repeating: "x", count: n) } let first = ArrayModification(replacing: x(10), at: 10, with: ["a", "b", "c"])! // final range of first: 10..<13 let m1 = ArrayModification(replacing: ["x", "x", "x", "x", "x"], at: 1, with: ["1", "2"]) XCTAssert(first.merged(with: m1!) == .disjunctOrderedBefore) let m2 = ArrayModification(replacing: ["x", "x", "x", "x", "x"], at: 5, with: ["1", "2"]) XCTAssert(first.merged(with: m2!) == .collapsedTo(.replaceSlice(x(15), at: 5, with: ["1", "2", "a", "b", "c"]))) let m3 = ArrayModification(replacing: ["x", "x", "x", "x", "x", "a"], at: 5, with: ["1", "2"]) XCTAssert(first.merged(with: m3!) == .collapsedTo(.replaceSlice(x(15), at: 5, with: ["1", "2", "b", "c"]))) let m4 = ArrayModification(replacing: ["x", "a", "b", "c", "x", "x"], at: 9, with: ["1", "2"]) XCTAssert(first.merged(with: m4!) == .collapsedTo(.replaceSlice(x(13), at: 9, with: ["1", "2"]))) let m5 = ArrayModification(replacing: [], at: 10, with: ["1", "2"]) XCTAssert(first.merged(with: m5!) == .collapsedTo(.replaceSlice(x(10), at: 10, with: ["1", "2", "a", "b", "c"]))) let m6 = ArrayModification(replacing: ["a"], at: 10, with: ["1", "2"]) XCTAssert(first.merged(with: m6!) == .collapsedTo(.replaceSlice(x(10), at: 10, with: ["1", "2", "b", "c"]))) let m7 = ArrayModification(replacing: ["a", "b", "c"], at: 10, with: ["1", "2"]) XCTAssert(first.merged(with: m7!) == .collapsedTo(.replaceSlice(x(10), at: 10, with: ["1", "2"]))) let m8 = ArrayModification(replacing: ["a", "b", "c", "x"], at: 10, with: ["1", "2"]) XCTAssert(first.merged(with: m8!) == .collapsedTo(.replaceSlice(x(11), at: 10, with: ["1", "2"]))) let m9 = ArrayModification(replacing: ["b"], at: 11, with: ["1", "2"]) XCTAssert(first.merged(with: m9!) == .collapsedTo(.replaceSlice(x(10), at: 10, with: ["a", "1", "2", "c"]))) let m10 = ArrayModification(replacing: ["b", "c", "x", "x"], at: 11, with: ["1", "2"]) XCTAssert(first.merged(with: m10!) == .collapsedTo(.replaceSlice(x(12), at: 10, with: ["a", "1", "2"]))) let m11 = ArrayModification(replacing: ["b", "c", "x", "x", "x", "x", "x", "x", "x"], at: 11, with: ["1", "2"]) XCTAssert(first.merged(with: m11!) == .collapsedTo(.replaceSlice(x(17), at: 10, with: ["a", "1", "2"]))) let m12 = ArrayModification(replacing: [], at: 13, with: ["1", "2"]) XCTAssert(first.merged(with: m12!) == .collapsedTo(.replaceSlice(x(10), at: 10, with: ["a", "b", "c", "1", "2"]))) let m13 = ArrayModification(replacing: ["x"], at: 13, with: ["1", "2"]) XCTAssert(first.merged(with: m13!) == .collapsedTo(.replaceSlice(x(11), at: 10, with: ["a", "b", "c", "1", "2"]))) let m14 = ArrayModification(replacing: [], at: 14, with: ["1", "2"]) XCTAssert(first.merged(with: m14!) == .disjunctOrderedAfter) let m15 = ArrayModification(replacing: x(6), at: 25, with: ["1", "2"]) XCTAssert(first.merged(with: m15!) == .disjunctOrderedAfter) } func testRemovingEqualChanges() { typealias M = ArrayModification XCTAssertEqual(M.insert("foo", at: 1).removingEqualChanges(), [M.insert("foo", at: 1)]) XCTAssertEqual(M.remove("foo", at: 1).removingEqualChanges(), [M.remove("foo", at: 1)]) XCTAssertEqual(M.replace("foo", at: 1, with: "bar").removingEqualChanges(), [M.replace("foo", at: 1, with: "bar")]) XCTAssertEqual(M.replace("foo", at: 1, with: "foo").removingEqualChanges(), []) XCTAssertEqual(M.replaceSlice(["foo"], at: 1, with: ["bar"]).removingEqualChanges(), [M.replace("foo", at: 1, with: "bar")]) XCTAssertEqual(M.replaceSlice(["foo", "bar"], at: 1, with: ["bar", "foo"]).removingEqualChanges(), [M.replaceSlice(["foo", "bar"], at: 1, with: ["bar", "foo"])]) XCTAssertEqual(M.replaceSlice(["foo", "bar", "baz"], at: 1, with: ["baz", "bar", "foo"]).removingEqualChanges(), [M.replace("foo", at: 1, with: "baz"), M.replace("baz", at: 3, with: "foo")]) XCTAssertEqual(M.replaceSlice(["foo", "bar", "baz"], at: 1, with: ["foo", "bar", "foo"]).removingEqualChanges(), [M.replace("baz", at: 3, with: "foo")]) XCTAssertEqual(M.replaceSlice(["foo", "bar", "baz"], at: 1, with: ["foo", "bar", "baz"]).removingEqualChanges(), []) } func testEquality() { typealias M = ArrayModification XCTAssertTrue(M.insert("foo", at: 1) == M.insert("foo", at: 1)) XCTAssertFalse(M.insert("foo", at: 1) != M.insert("foo", at: 1)) XCTAssertFalse(M.insert("foo", at: 1) == M.insert("bar", at: 1)) XCTAssertFalse(M.insert("foo", at: 1) == M.insert("foo", at: 2)) XCTAssertTrue(M.insert("foo", at: 1) == M.replaceSlice([], at: 1, with: ["foo"])) } func testIsIdentity() { typealias M = ArrayModification XCTAssertFalse(M.insert("foo", at: 1).isIdentity) XCTAssertFalse(M.remove("foo", at: 1).isIdentity) XCTAssertFalse(M.replace("foo", at: 1, with: "bar").isIdentity) XCTAssertTrue(M.replace("foo", at: 1, with: "foo").isIdentity) XCTAssertFalse(M.replaceSlice(["foo", "bar"], at: 1, with: ["bar", "foo"]).isIdentity) XCTAssertFalse(M.replaceSlice(["foo", "bar"], at: 1, with: ["foo", "foo"]).isIdentity) XCTAssertTrue(M.replaceSlice(["foo", "bar"], at: 1, with: ["foo", "bar"]).isIdentity) } func testDescription() { typealias M = ArrayModification XCTAssertEqual(M.insert("foo", at: 1).description, ".insert(foo, at: 1)") XCTAssertEqual(M.remove("foo", at: 1).description, ".remove(foo, at: 1)") XCTAssertEqual(M.replace("foo", at: 1, with: "bar").description, ".replace(foo, at: 1, with: bar)") XCTAssertEqual(M.replaceSlice(["foo", "bar"], at: 1, with: ["bar", "foo"]).description, ".replaceSlice([foo, bar], at: 1, with: [bar, foo])") XCTAssertEqual(M.insert("foo", at: 1).debugDescription, ".insert(\"foo\", at: 1)") XCTAssertEqual(M.remove("foo", at: 1).debugDescription, ".remove(\"foo\", at: 1)") XCTAssertEqual(M.replace("foo", at: 1, with: "bar").debugDescription, ".replace(\"foo\", at: 1, with: \"bar\")") XCTAssertEqual(M.replaceSlice(["foo", "bar"], at: 1, with: ["bar", "foo"]).debugDescription, ".replaceSlice([\"foo\", \"bar\"], at: 1, with: [\"bar\", \"foo\"])") } } ================================================ FILE: Tests/GlueKitTests/ArrayReferenceTests.swift ================================================ // // ArrayReferenceTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-11-02. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest import GlueKit class ArrayReferenceTests: XCTestCase { func testReference() { let a: ArrayVariable = [1, 2, 3] let b: ArrayVariable = [10, 20] let c: ArrayVariable = [] let ref = Variable>(a) XCTAssertEqual(ref.value.value, [1, 2, 3]) a[1] = 4 XCTAssertEqual(ref.value.value, [1, 4, 3]) let unpacked = ref.unpacked() XCTAssertEqual(unpacked.isBuffered, false) XCTAssertEqual(unpacked.count, 3) XCTAssertEqual(unpacked[0], 1) XCTAssertEqual(Array(unpacked[0 ..< 3]), [1, 4, 3]) XCTAssertEqual(unpacked.value, [1, 4, 3]) a[0] = 2 XCTAssertEqual(unpacked.value, [2, 4, 3]) let sink = MockArrayObserver(unpacked) sink.expecting(["begin", "3.replace(3, at: 2, with: 6)", "end"]) { a[2] = 6 } sink.expecting(["begin", "3.replaceSlice([2, 4, 6], at: 0, with: [10, 20])", "end"]) { ref.value = b } sink.expecting("begin") { b.apply(.beginTransaction) } sink.expectingNothing { ref.apply(.beginTransaction) } sink.expecting("2.insert(15, at: 1)") { b.insert(15, at: 1) } sink.expecting("3.replaceSlice([10, 15, 20], at: 0, with: [])") { ref.value = c } sink.expecting("0.insert(100, at: 0)") { c.append(100) } sink.expectingNothing { b.apply(.endTransaction) } sink.expecting("end") { ref.apply(.endTransaction) } sink.disconnect() } } ================================================ FILE: Tests/GlueKitTests/ArrayVariableTests.swift ================================================ // // ArrayVariableTests.swift // GlueKit // // Created by Károly Lőrentey on 2015-12-09. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest import GlueKit class ArrayVariableTests: XCTestCase { func testArrayInitialization() { let a0 = ArrayVariable() // Empty XCTAssertEqual(a0.count, 0) let a1 = ArrayVariable([1, 2, 3, 4]) XCTAssertEqual(a1.value, [1, 2, 3, 4]) let a2 = ArrayVariable(1 ... 4) XCTAssertEqual(a2.value, [1, 2, 3, 4]) let a3 = ArrayVariable(elements: 1, 2, 3, 4) XCTAssertEqual(a3.value, [1, 2, 3, 4]) let a4: ArrayVariable = [1, 2, 3, 4] // From array literal XCTAssertEqual(a4.value, [1, 2, 3, 4]) } func testEquality() { // Equality tests between two ArrayVariables XCTAssertTrue(ArrayVariable([1, 2, 3]).value == ArrayVariable([1, 2, 3]).value) XCTAssertFalse(ArrayVariable([1, 2, 3]).value == ArrayVariable([1, 2]).value) // Equality tests between ArrayVariable and an array literal XCTAssertTrue(ArrayVariable([1, 2, 3]).value == [1, 2, 3]) XCTAssertFalse(ArrayVariable([1, 2, 3]).value == [1, 2]) // Equality tests between two different ObservableArrayTypes XCTAssertTrue(ArrayVariable([1, 2, 3]).value == AnyObservableArray(ArrayVariable([1, 2, 3])).value) XCTAssertFalse(ArrayVariable([1, 2, 3]).value == AnyObservableArray(ArrayVariable([1, 2])).value) // Equality tests between an ArrayVariable and an Array XCTAssertTrue(ArrayVariable([1, 2, 3]).value == Array([1, 2, 3])) XCTAssertFalse(ArrayVariable([1, 2, 3]).value == Array([1, 2])) XCTAssertTrue(Array([1, 2, 3]) == ArrayVariable([1, 2, 3]).value) XCTAssertFalse(Array([1, 2]) == ArrayVariable([1, 2, 3]).value) } func testValueAndCount() { let array: ArrayVariable = [1, 2, 3] XCTAssertEqual(array.value, [1, 2, 3]) XCTAssertEqual(array.count, 3) array.value = [4, 5] XCTAssertEqual(array.value, [4, 5]) XCTAssertEqual(array.count, 2) array.value = [6] XCTAssertEqual(array.value, [6]) XCTAssertEqual(array.count, 1) } func testIndexing() { let array: ArrayVariable = [1, 2, 3] XCTAssertEqual(array[1], 2) array[2] = 10 XCTAssertEqual(array[2], 10) XCTAssert([1, 2, 10].elementsEqual(array.value, by: ==)) } func testIndexingWithRanges() { let array: ArrayVariable = [1, 2, 3, 4] XCTAssertEqual(array[1..<3], [2, 3]) array[1..<3] = [20, 30, 40] XCTAssertEqual(array.value, [1, 20, 30, 40, 4]) } func testChangeNotifications() { func tryCase(_ input: [Int], op: (ArrayVariable) -> (), expectedOutput: [Int], expectedChange: ArrayChange) { let array = ArrayVariable(input) var changes = [ArrayChange]() var values = [[Int]]() let c1 = array.changes.subscribe { changes.append($0) } defer { c1.disconnect() } let c2 = array.anyObservableValue.futureValues.subscribe { values.append($0) } defer { c2.disconnect() } op(array) XCTAssertEqual(array.value, expectedOutput) XCTAssertEqual(values, [expectedOutput]) XCTAssertTrue(changes.count == 1 && changes[0] == expectedChange) } tryCase([1, 2, 3], op: { $0[1] = 20 }, expectedOutput: [1, 20, 3], expectedChange: ArrayChange(initialCount: 3, modification: .replace(2, at: 1, with: 20))) tryCase([1, 2, 3], op: { $0[1..<2] = [20, 30] }, expectedOutput: [1, 20, 30, 3], expectedChange: ArrayChange(initialCount: 3, modification: .replaceSlice([2], at: 1, with: [20, 30]))) tryCase([1, 2, 3], op: { $0.value = [4, 5] }, expectedOutput: [4, 5], expectedChange: ArrayChange(initialCount: 3, modification: .replaceSlice([1, 2, 3], at: 0, with: [4, 5]))) tryCase([1, 2, 3], op: { $0.value = [4, 5] }, expectedOutput: [4, 5], expectedChange: ArrayChange(initialCount: 3, modification: .replaceSlice([1, 2, 3], at: 0, with: [4, 5]))) tryCase([1, 2, 3], op: { $0.replaceSubrange(0..<2, with: [5, 6, 7]) }, expectedOutput: [5, 6, 7, 3], expectedChange: ArrayChange(initialCount: 3, modification: .replaceSlice([1, 2], at: 0, with: [5, 6, 7]))) tryCase([1, 2, 3], op: { $0.append(10) }, expectedOutput: [1, 2, 3, 10], expectedChange: ArrayChange(initialCount: 3, modification: .insert(10, at: 3))) tryCase([1, 2, 3], op: { $0.insert(10, at: 2) }, expectedOutput: [1, 2, 10, 3], expectedChange: ArrayChange(initialCount: 3, modification: .insert(10, at: 2))) tryCase([1, 2, 3], op: { $0.remove(at: 1) }, expectedOutput: [1, 3], expectedChange: ArrayChange(initialCount: 3, modification: .remove(2, at: 1))) tryCase([1, 2, 3], op: { $0.removeFirst() }, expectedOutput: [2, 3], expectedChange: ArrayChange(initialCount: 3, modification: .remove(1, at: 0))) tryCase([1, 2, 3], op: { $0.removeLast() }, expectedOutput: [1, 2], expectedChange: ArrayChange(initialCount: 3, modification: .remove(3, at: 2))) tryCase([1, 2, 3], op: { $0.removeAll() }, expectedOutput: [], expectedChange: ArrayChange(initialCount: 3, modification: .replaceSlice([1, 2, 3], at: 0, with: []))) } } ================================================ FILE: Tests/GlueKitTests/Bookshelf.swift ================================================ // // Bookshelf.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-02. // Copyright © 2015–2017 Károly Lőrentey. // import Foundation import GlueKit import XCTest // Let's suppose we're writing an app for maintaining a catalogue for your books. // Here is what the model could look like. private class Author: Hashable { let name: Variable let yearOfBirth: Variable init(name: String, yearOfBirth: Int) { self.name = .init(name) self.yearOfBirth = .init(yearOfBirth) } var hashValue: Int { return name.value.hashValue } static func == (a: Author, b: Author) -> Bool { return a.name.value == b.name.value && a.yearOfBirth.value == b.yearOfBirth.value } } private class Book: Hashable { let title: Variable let authors: SetVariable let publicationYear: Variable let pages: Variable init(title: String, authors: Set, publicationYear: Int, pages: Int) { self.title = .init(title) self.authors = SetVariable(authors) self.publicationYear = .init(pages) self.pages = .init(pages) } var hashValue: Int { return title.value.hashValue } static func == (a: Book, b: Book) -> Bool { return (a.title.value == b.title.value && a.authors.value == b.authors.value && a.publicationYear.value == b.publicationYear.value && a.pages.value == b.pages.value) } } private class Bookshelf { let location: Variable let books: ArrayVariable init(location: String, books: [Book] = []) { self.location = .init(location) self.books = .init(books) } } private struct Fixture { let stephenson = Author(name: "Neal Stephenson", yearOfBirth: 1959) let pratchett = Author(name: "Terry Pratchett", yearOfBirth: 1948) let gaiman = Author(name: "Neil Gaiman", yearOfBirth: 1960) let knuth = Author(name: "Donald E. Knuth", yearOfBirth: 1938) lazy var colourOfMagic: Book = .init(title: "The Colour of Magic", authors: [self.pratchett], publicationYear: 1983, pages: 206) lazy var smallGods: Book = .init(title: "Small Gods", authors: [self.pratchett], publicationYear: 1992, pages: 284) lazy var seveneves: Book = .init(title: "Seveneves", authors: [self.stephenson], publicationYear: 2015, pages: 880) lazy var goodOmens: Book = .init(title: "Good Omens", authors: [self.pratchett, self.gaiman], publicationYear: 1990, pages: 288) lazy var americanGods: Book = .init(title: "American Gods", authors: [self.gaiman], publicationYear: 2001, pages: 465) lazy var cryptonomicon: Book = .init(title: "Cryptonomicon", authors: [self.stephenson], publicationYear: 1999, pages: 918) lazy var anathem: Book = .init(title: "Anathem", authors: [self.stephenson], publicationYear: 2008, pages: 928) lazy var texBook: Book = .init(title: "The TeXBook", authors: [self.knuth], publicationYear: 1984, pages: 483) lazy var taocp1: Book = .init(title: "The Art of Computer Programming vol. 1: Fundamental Algorithms. 3rd ed.", authors: [self.knuth], publicationYear: 1997, pages: 650) lazy var topShelf: Bookshelf = .init(location: "Top", books: [self.colourOfMagic, self.smallGods, self.seveneves, self.goodOmens, self.americanGods]) lazy var bottomShelf: Bookshelf = .init(location: "Bottom", books: [self.cryptonomicon, self.anathem, self.texBook, self.taocp1]) lazy var shelves: ArrayVariable = [self.topShelf, self.bottomShelf] } class BookshelfTests: XCTestCase { func testAllTitles() { var f = Fixture() // Let's get an array of the title of each book in the library. let allTitles = f.shelves.flatMap{$0.books}.map{$0.title} XCTAssertEqual(allTitles.value, ["The Colour of Magic", "Small Gods", "Seveneves", "Good Omens", "American Gods", "Cryptonomicon", "Anathem", "The TeXBook", "The Art of Computer Programming vol. 1: Fundamental Algorithms. 3rd ed."]) } func testBooksByStephenson() { var f = Fixture() let booksByStephenson = f.shelves.flatMap{$0.books}.filter { book in book.authors.observableContains(f.stephenson) } XCTAssertEqual(booksByStephenson.value, [f.seveneves, f.cryptonomicon, f.anathem]) } } ================================================ FILE: Tests/GlueKitTests/BracketingSourceTests.swift ================================================ // // BracketingSourceTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-26. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest import GlueKit class BracketingSourceTests: XCTestCase { func testHello() { let source = Signal() var helloCount = 0 let bracket = source.bracketed(hello: { helloCount += 1; return 0 }, goodbye: { return nil }) XCTAssertFalse(source.isConnected) let s1 = MockSink() s1.expecting(0) { bracket.add(s1) } XCTAssertTrue(source.isConnected) XCTAssertEqual(helloCount, 1) s1.expecting(1) { source.send(1) } let s2 = MockSink() s2.expecting(0) { bracket.add(s2) } XCTAssertEqual(helloCount, 2) s1.expecting(2) { s2.expecting(2) { source.send(2) } } s1.expectingNothing { _ = bracket.remove(s1) } s2.expecting(3) { source.send(3) } s2.expectingNothing { _ = bracket.remove(s2) } XCTAssertFalse(source.isConnected) XCTAssertEqual(helloCount, 2) } func testGoodbye() { let source = Signal() var goodbyeCount = 0 let bracket = source.bracketed(hello: { return nil }, goodbye: { goodbyeCount += 1; return 0 }) XCTAssertFalse(source.isConnected) let s1 = MockSink() s1.expectingNothing { bracket.add(s1) } XCTAssertTrue(source.isConnected) XCTAssertEqual(goodbyeCount, 0) s1.expecting(1) { source.send(1) } let s2 = MockSink() s2.expectingNothing { bracket.add(s2) } XCTAssertEqual(goodbyeCount, 0) s1.expecting(2) { s2.expecting(2) { source.send(2) } } s1.expecting(0) { _ = bracket.remove(s1) } XCTAssertEqual(goodbyeCount, 1) s2.expecting(3) { source.send(3) } s2.expecting(0) { _ = bracket.remove(s2) } XCTAssertFalse(source.isConnected) XCTAssertEqual(goodbyeCount, 2) } } ================================================ FILE: Tests/GlueKitTests/BufferedSourceTests.swift ================================================ // // BufferedSourceTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-26. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest import GlueKit private class TestSource: SourceType { typealias Value = Int var added = 0 var removed = 0 var sinks: Set> = [] init() {} func add(_ sink: Sink) where Sink.Value == Int { added += 1 let (inserted, _) = sinks.insert(sink.anySink) precondition(inserted) } @discardableResult func remove(_ sink: Sink) -> Sink where Sink.Value == Int { removed += 1 let old = sinks.remove(sink.anySink)! return old.opened()! } func send(_ value: Int) { for sink in sinks { if sinks.contains(sink) { sink.receive(value) } } } } class BufferedSourceTests: XCTestCase { func testRetainsInput() { weak var weakSource: TestSource? = nil var buffered: AnySource? = nil do { let source = TestSource() weakSource = source buffered = source.buffered() } XCTAssertNotNil(weakSource) XCTAssertNotNil(buffered) buffered = nil XCTAssertNil(weakSource) } func testSubscribesOnceWhileActive() { let source = TestSource() let buffered = source.buffered() XCTAssertEqual(source.added, 0) XCTAssertEqual(source.removed, 0) let sink = MockSink() buffered.add(sink) XCTAssertEqual(source.added, 1) XCTAssertEqual(source.removed, 0) let sink2 = MockSink() buffered.add(sink2) XCTAssertEqual(source.added, 1) XCTAssertEqual(source.removed, 0) buffered.remove(sink) XCTAssertEqual(source.added, 1) XCTAssertEqual(source.removed, 0) buffered.remove(sink2) XCTAssertEqual(source.added, 1) XCTAssertEqual(source.removed, 1) withExtendedLifetime(buffered) {} } func testReceivesValuesFromSource() { let source = TestSource() let buffered = source.buffered() let sink = MockSink() buffered.add(sink) sink.expecting(1) { source.send(1) } let sink2 = MockSink() buffered.add(sink2) sink.expecting(2) { sink2.expecting(2) { source.send(2) } } buffered.remove(sink) sink2.expecting(3) { source.send(3) } buffered.remove(sink2) sink.expectingNothing { sink2.expectingNothing { source.send(4) } } } } ================================================ FILE: Tests/GlueKitTests/ChangeTests.swift ================================================ // // Abstract Observables.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-26. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest import GlueKit class ChangeTests: XCTestCase { func testDefaultApplied() { let change = TestChange([1, 2]) XCTAssertEqual(change.applied(on: 1), 2) } func testDefaultMerged() { let change = TestChange([1, 2]) let next = TestChange([2, 3]) XCTAssertEqual(change.merged(with: next).values, [1, 2, 3]) } } ================================================ FILE: Tests/GlueKitTests/ChangesSourceTests.swift ================================================ // // ChangesSourceTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-26. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest @testable import GlueKit class ChangesSourceTests: XCTestCase { func testRetainsObservable() { weak var weakObservable: TestObservable? = nil var changes: AnySource? = nil do { let observable = TestObservable(0) weakObservable = observable changes = observable.changes } XCTAssertNotNil(changes) XCTAssertNotNil(weakObservable) changes = nil XCTAssertNil(weakObservable) } func testSubscribingToChangesSubscribesToUpdates() { let observable = TestObservable(0) let changes = observable.changes let sink = MockSink() XCTAssertFalse(observable.isConnected) changes.add(sink) XCTAssertTrue(observable.isConnected) changes.remove(sink) XCTAssertFalse(observable.isConnected) } func testChangesSendsCompletedChanges() { let observable = TestObservable(0) let changes = observable.changes let sink = MockSink() changes.add(sink) sink.expectingNothing { observable.beginTransaction() observable.value = 1 observable.value = 2 } sink.expecting(TestChange([0, 1, 2])) { observable.endTransaction() } changes.remove(sink) } func testRemovingASinkDuringATransactionSendsPartialChanges() { let observable = TestObservable(0) let changes = observable.changes let sink = MockSink() changes.add(sink) observable.beginTransaction() observable.value = 1 observable.value = 2 sink.expecting(TestChange([0, 1, 2])) { _ = changes.remove(sink) } observable.value = 3 observable.endTransaction() } func testDifferentSinksMayReceiveDifferentChanges() { let observable = TestObservable(0) let changes = observable.changes let sink1 = MockSink() changes.add(sink1) observable.beginTransaction() observable.value = 1 let sink2 = MockSink() changes.add(sink2) observable.value = 2 sink1.expecting(TestChange([0, 1, 2])) { _ = changes.remove(sink1) } observable.value = 3 sink2.expecting(TestChange([1, 2, 3])) { observable.endTransaction() } changes.remove(sink2) } } ================================================ FILE: Tests/GlueKitTests/CombinedObservableTests.swift ================================================ // // CombinedObservableTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-09. // Copyright © 2015–2017 Károly Lőrentey. // import Foundation import XCTest import GlueKit class CombinedObservableTests: XCTestCase { func testCombine_Works() { let a = TestObservableValue(1) let b = TestObservableValue(2) let combined = a.combined(b) { a, b in "[\(a),\(b)]" } let mock = MockValueUpdateSink(combined) mock.expecting(["begin", "[1,2] -> [3,2]", "end"]) { a.value = 3 } XCTAssertEqual(combined.value, "[3,2]") mock.expecting(["begin", "[3,2] -> [3,4]", "end"]) { b.value = 4 } XCTAssertEqual(combined.value, "[3,4]") } func testCombineDistinct_WithNestedUpdates() { let a = TestObservableValue(3) let b = TestObservableValue(2) let combined = a.combined(b) { a, b in (a, b) }.distinct(==) var r = "" let c = combined.values.subscribe { av, bv in r += " (\(av)-\(bv)" if av > 0 { a.value = av - 1 } else if bv > 0 { b.value = bv - 1 } r += ")" } XCTAssertEqual(r, " (3-2) (2-2) (1-2) (0-2) (0-1) (0-0)") c.disconnect() } func testCombineUpToSixObservables() { let a = TestObservableValue(1) let b = TestObservableValue(2) let c = TestObservableValue(3) let d = TestObservableValue(4) let e = TestObservableValue(5) let f = TestObservableValue(6) let t2 = a.combined(b) let t3 = a.combined(b, c) let t4 = a.combined(b, c, d) let t5 = a.combined(b, c, d, e) let t6 = a.combined(b, c, d, e, f) let c2 = a.combined(b, by: { (a: Int, b: Int) -> Int in a + b }) let c3 = a.combined(b, c, by: { (a: Int, b: Int, c: Int) -> Int in a + b + c }) let c4 = a.combined(b, c, d, by: { (a: Int, b: Int, c: Int, d: Int) -> Int in a + b + c + d }) let c5 = a.combined(b, c, d, e, by: { (a: Int, b: Int, c: Int, d: Int, e: Int) -> Int in a + b + c + d + e }) let c6 = a.combined(b, c, d, e, f, by: { (a: Int, b: Int, c: Int, d: Int, e: Int, f: Int) -> Int in a + b + c + d + e + f }) XCTAssertTrue(t2.value == (1, 2)) XCTAssertTrue(t3.value == (1, 2, 3)) XCTAssertTrue(t4.value == (1, 2, 3, 4)) XCTAssertTrue(t5.value == (1, 2, 3, 4, 5)) XCTAssertTrue(t6.value == (1, 2, 3, 4, 5, 6)) XCTAssertTrue(c2.value == 3) XCTAssertTrue(c3.value == 6) XCTAssertTrue(c4.value == 10) XCTAssertTrue(c5.value == 15) XCTAssertTrue(c6.value == 21) } func testEquatableOperators() { let a = TestObservableValue(0) let b = TestObservableValue(0) let eq = (a == b) let ne = (a != b) XCTAssertTrue(eq.value) XCTAssertFalse(ne.value) a.value = 1 XCTAssertFalse(eq.value) XCTAssertTrue(ne.value) } func testComparableOperators() { let a = TestObservableValue(0) let b = TestObservableValue(0) let lt = (a < b) let gt = (a > b) let le = (a <= b) let ge = (a >= b) let mi = min(a, b) let ma = max(a, b) XCTAssertFalse(lt.value) XCTAssertFalse(gt.value) XCTAssertTrue(le.value) XCTAssertTrue(ge.value) XCTAssertEqual(mi.value, 0) XCTAssertEqual(ma.value, 0) a.value = 1 XCTAssertFalse(lt.value) XCTAssertTrue(gt.value) XCTAssertFalse(le.value) XCTAssertTrue(ge.value) XCTAssertEqual(mi.value, 0) XCTAssertEqual(ma.value, 1) b.value = 2 XCTAssertTrue(lt.value) XCTAssertFalse(gt.value) XCTAssertTrue(le.value) XCTAssertFalse(ge.value) XCTAssertEqual(mi.value, 1) XCTAssertEqual(ma.value, 2) a.value = 2 XCTAssertFalse(lt.value) XCTAssertFalse(gt.value) XCTAssertTrue(le.value) XCTAssertTrue(ge.value) XCTAssertEqual(mi.value, 2) XCTAssertEqual(ma.value, 2) } func testBooleanOperators() { let a = TestUpdatableValue(0) let b = TestUpdatableValue(0) let c = TestUpdatableValue(0) let bIsBetweenAAndC = a < b && b < c let bIsNotBetweenAAndC = !bIsBetweenAAndC let aIsNotTheGreatest = a < b || a < c XCTAssertFalse(bIsBetweenAAndC.value) XCTAssertTrue(bIsNotBetweenAAndC.value) XCTAssertFalse(aIsNotTheGreatest.value) a.value = 1 XCTAssertFalse(bIsBetweenAAndC.value) XCTAssertTrue(bIsNotBetweenAAndC.value) XCTAssertFalse(aIsNotTheGreatest.value) b.value = 2 XCTAssertFalse(bIsBetweenAAndC.value) XCTAssertTrue(bIsNotBetweenAAndC.value) XCTAssertTrue(aIsNotTheGreatest.value) c.value = 3 XCTAssertTrue(bIsBetweenAAndC.value) XCTAssertFalse(bIsNotBetweenAAndC.value) XCTAssertTrue(aIsNotTheGreatest.value) } func testIntegerNegation() { let a = TestUpdatableValue(1) let n = -a XCTAssertEqual(n.value, -1) let mock = MockValueUpdateSink(n) mock.expecting(["begin", "-1 -> -2", "end"]) { a.value = 2 } } func testIntegerArithmeticOperators() { let a = TestUpdatableValue(0) let b = TestUpdatableValue(0) let c = TestUpdatableValue(0) let e1 = a % AnyObservableValue.constant(10) let e2 = b * c / (a + AnyObservableValue.constant(1)) let expression = e1 + e2 - c var r = [Int]() let connection = expression.values.subscribe { r.append($0) } XCTAssertEqual(r, [0]) r = [] a.value = 1 // The observable `a` occurs twice in the expression above, so the expression will be evaluated twice. // The first evaluation will only apply the new value to one of the sources. // However, the `values` source reports only full transactions, so such partial updates will not appear there. XCTAssertEqual(r, [1]) r = [] b.value = 2 XCTAssertEqual(r, [1]) r = [] c.value = 3 XCTAssertEqual(r, [1 + 2 * 3 / 2 - 3] as [Int]) r = [] a.value = 15 XCTAssertEqual(r, [5 + 2 * 3 / 16 - 3] as [Int]) r = [] connection.disconnect() } func testFloatingPointArithmeticOperators() { let a = TestUpdatableValue(0.0) let b = TestUpdatableValue(0.0) let c = TestUpdatableValue(0.0) let expression = a + b * c / (a + AnyObservableValue.constant(1)) - c XCTAssertEqual(expression.value, 0) var r: [Double] = [] let connection = expression.values.subscribe { r.append($0) } XCTAssertEqual(r, [0]) r = [] a.value = 1 XCTAssertEqual(r, [1]) r = [] b.value = 2 XCTAssertEqual(r, [1]) r = [] c.value = 3 XCTAssertEqual(r, [1 + 2 * 3 / 2 - 3] as [Double]) r = [] a.value = 15 XCTAssertEqual(r, [15 + 2 * 3 / 16 - 3] as [Double]) r = [] connection.disconnect() } func testTransactions() { let a = Variable(0) let sum = a + a let sink = MockValueUpdateSink(sum) sink.expecting(["begin", "0 -> 1", "1 -> 2", "end"]) { a.value = 1 } sink.expecting("begin") { a.apply(.beginTransaction) } sink.expecting(["2 -> 3", "3 -> 4"]) { a.value = 2 } sink.expecting("end") { a.apply(.endTransaction) } sink.disconnect() } } ================================================ FILE: Tests/GlueKitTests/CombinedUpdatableTests.swift ================================================ // // CombinedUpdatableTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-09. // Copyright © 2015–2017 Károly Lőrentey. // import Foundation import XCTest import GlueKit class CombinedUpdatableTests: XCTestCase { func testCombine_Works() { let a = TestUpdatableValue(0) let b = TestUpdatableValue(1) let combined = a.combined(b).map({ (a, b) in 10 * a + b }, inverse: { ($0 / 10, $0 % 10) }) XCTAssertEqual(combined.value, 1) combined.value = 12 XCTAssertEqual(combined.value, 12) let changes = combined.changes let mock = TransformedMockSink(changes, { "\($0)" }) mock.expecting("12 -> 32") { a.value = 3 } XCTAssertEqual(combined.value, 32) mock.expecting("32 -> 34") { b.value = 4 } XCTAssertEqual(combined.value, 34) mock.expecting("34 -> 56") { combined.value = 56 } XCTAssertEqual(combined.value, 56) XCTAssertEqual(a.value, 5) XCTAssertEqual(b.value, 6) } func testCombine_WithNestedUpdates() { let a = TestUpdatableValue(3) let b = TestUpdatableValue(2) let combined = a.combined(b) var r = "" let c = combined.values.subscribe { av, bv in r += " (\(av)-\(bv)" if av > 0 { a.value = av - 1 } else if bv > 0 { b.value = bv - 1 } r += ")" } XCTAssertEqual(r, " (3-2) (2-2) (1-2) (0-2) (0-1) (0-0)") c.disconnect() } func testCombineUpToSixUpdatables() { let a = TestUpdatableValue(1) let b = TestUpdatableValue(2) let c = TestUpdatableValue(3) let d = TestUpdatableValue(4) let e = TestUpdatableValue(5) let f = TestUpdatableValue(6) let t2 = a.combined(b) let t3 = a.combined(b, c) let t4 = a.combined(b, c, d) let t5 = a.combined(b, c, d, e) let t6 = a.combined(b, c, d, e, f) XCTAssertTrue(t2.value == (1, 2)) XCTAssertTrue(t3.value == (1, 2, 3)) XCTAssertTrue(t4.value == (1, 2, 3, 4)) XCTAssertTrue(t5.value == (1, 2, 3, 4, 5)) XCTAssertTrue(t6.value == (1, 2, 3, 4, 5, 6)) t6.value = (6, 5, 4, 3, 2, 1) XCTAssertTrue(t2.value == (6, 5)) XCTAssertTrue(t3.value == (6, 5, 4)) XCTAssertTrue(t4.value == (6, 5, 4, 3)) XCTAssertTrue(t5.value == (6, 5, 4, 3, 2)) XCTAssertTrue(t6.value == (6, 5, 4, 3, 2, 1)) t5.value = (2, 4, 6, 8, 10) XCTAssertTrue(t2.value == (2, 4)) XCTAssertTrue(t3.value == (2, 4, 6)) XCTAssertTrue(t4.value == (2, 4, 6, 8)) XCTAssertTrue(t5.value == (2, 4, 6, 8, 10)) XCTAssertTrue(t6.value == (2, 4, 6, 8, 10, 1)) t4.value = (3, 5, 7, 11) XCTAssertTrue(t2.value == (3, 5)) XCTAssertTrue(t3.value == (3, 5, 7)) XCTAssertTrue(t4.value == (3, 5, 7, 11)) XCTAssertTrue(t5.value == (3, 5, 7, 11, 10)) XCTAssertTrue(t6.value == (3, 5, 7, 11, 10, 1)) t3.value = (-1, -2, -3) XCTAssertTrue(t2.value == (-1, -2)) XCTAssertTrue(t3.value == (-1, -2, -3)) XCTAssertTrue(t4.value == (-1, -2, -3, 11)) XCTAssertTrue(t5.value == (-1, -2, -3, 11, 10)) XCTAssertTrue(t6.value == (-1, -2, -3, 11, 10, 1)) t2.value = (10, 20) XCTAssertTrue(t2.value == (10, 20)) XCTAssertTrue(t3.value == (10, 20, -3)) XCTAssertTrue(t4.value == (10, 20, -3, 11)) XCTAssertTrue(t5.value == (10, 20, -3, 11, 10)) XCTAssertTrue(t6.value == (10, 20, -3, 11, 10, 1)) a.value = 0 XCTAssertTrue(t2.value == (0, 20)) XCTAssertTrue(t3.value == (0, 20, -3)) XCTAssertTrue(t4.value == (0, 20, -3, 11)) XCTAssertTrue(t5.value == (0, 20, -3, 11, 10)) XCTAssertTrue(t6.value == (0, 20, -3, 11, 10, 1)) } } ================================================ FILE: Tests/GlueKitTests/ConnectorTests.swift ================================================ // // ConnectorTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-10. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest @testable import GlueKit class TestConnection: Connection { var callback: (() -> ())? init(_ callback: @escaping () -> ()) { self.callback = callback super.init() } deinit { disconnect() } override func disconnect() { guard let callback = self.callback else { return } self.callback = nil callback() } } class ConnectorTests: XCTestCase { func test_EmptyConnector() { let connector = Connector() connector.disconnect() } func test_ReleasingTheConnectorDisconnectsItsConnections() { var actual: [Int] = [] do { let connector = Connector() let c = TestConnection { actual.append(1) } c.putInto(connector) XCTAssertEqual(actual, []) } XCTAssertEqual(actual, [1]) } func test_DisconnectingTheConnectorDisconnectsItsConnections() { var actual: [Int] = [] do { let connector = Connector() let c = TestConnection { actual.append(1) } c.putInto(connector) XCTAssertEqual(actual, []) connector.disconnect() XCTAssertEqual(actual, [1]) withExtendedLifetime(connector) {} } XCTAssertEqual(actual, [1]) } func test_ConnectorsCanBeRestarted() { var actual: [Int] = [] do { let connector = Connector() let c1 = TestConnection { actual.append(1) } c1.putInto(connector) XCTAssertEqual(actual, []) connector.disconnect() XCTAssertEqual(actual, [1]) let c2 = TestConnection { actual.append(2) } c2.putInto(connector) XCTAssertEqual(actual, [1]) connector.disconnect() XCTAssertEqual(actual, [1, 2]) withExtendedLifetime(connector) {} } XCTAssertEqual(actual, [1, 2]) } func test_ConnectingASourceToAClosure() { let signal = Signal() let connector = Connector() var expected: [Int] = [] var actual: [Int] = [] connector.connect(signal) { value in actual.append(value) } XCTAssertEqual(actual, expected) expected.append(42) signal.send(42) XCTAssertEqual(actual, expected) connector.disconnect() signal.send(23) XCTAssertEqual(actual, expected) withExtendedLifetime(connector) {} } #if false // TODO Compiler crash in Xcode 8.3.2 func test_ConnectingAnObservableToAChangeClosure() { let variable = Variable(0) let connector = Connector() var expected: [ValueChange] = [] var actual: [ValueChange] = [] connector.connect(variable) { change in actual.append(change) } XCTAssertTrue(actual.elementsEqual(expected, by: ==)) expected.append(.init(from: 0, to: 42)) variable.value = 42 XCTAssertTrue(actual.elementsEqual(expected, by: ==)) connector.disconnect() variable.value = 23 XCTAssertTrue(actual.elementsEqual(expected, by: ==)) withExtendedLifetime(connector) {} } #endif } ================================================ FILE: Tests/GlueKitTests/DispatchSourceTests.swift ================================================ // // DispatchSourceTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-11-02. // Copyright © 2015–2017 Károly Lőrentey. // import Foundation import XCTest import GlueKit class DispatchSourceTests: XCTestCase { func testDispatchQueue() { let signal = Signal() let queue = DispatchQueue(label: "org.attaswift.GlueKit.test") let semaphore = DispatchSemaphore(value: 1) var r: [Int] = [] let connection = signal.dispatch(on: queue).subscribe { value in semaphore.wait() r.append(value) semaphore.signal() } semaphore.wait() signal.send(1) XCTAssertEqual(r, []) semaphore.signal() queue.sync { XCTAssertEqual(r, [1]) } connection.disconnect() } func testOperationQueue() { let signal = Signal() let queue = OperationQueue() queue.maxConcurrentOperationCount = 1 let semaphore = DispatchSemaphore(value: 1) var r: [Int] = [] let connection = signal.dispatch(on: queue).subscribe { value in XCTAssertEqual(OperationQueue.current, queue) semaphore.wait() r.append(value) semaphore.signal() } semaphore.wait() signal.send(1) XCTAssertEqual(r, []) semaphore.signal() queue.waitUntilAllOperationsAreFinished() XCTAssertEqual(r, [1]) queue.addOperation { signal.send(2) } queue.waitUntilAllOperationsAreFinished() semaphore.wait() XCTAssertEqual(r, [1, 2]) semaphore.signal() connection.disconnect() } } ================================================ FILE: Tests/GlueKitTests/DistinctTests.swift ================================================ // // DistinctTests.swift // GlueKit // // Created by Károly Lőrentey on 2015-12-08. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest @testable import GlueKit class DistinctTests: XCTestCase { func test_updates_reportsChangesAtTheEndOfTheTransaction() { let test = TestUpdatableValue(0) let distinct = test.distinct() let sink = MockValueUpdateSink() distinct.updates.add(sink) sink.expecting("begin") { test.beginTransaction() } sink.expectingNothing { test.value = 1 test.value = 2 } sink.expecting(["0 -> 2", "end"]) { test.endTransaction() } distinct.updates.remove(sink) } func test_updates_ignoresTransactionsThatDontChangeTheValue() { let test = TestUpdatableValue(0) let distinct = test.distinct() let sink = MockValueUpdateSink() distinct.updates.add(sink) sink.expecting("begin") { test.beginTransaction() } sink.expectingNothing { test.value = 1 test.value = 0 } sink.expecting(["end"]) { test.endTransaction() } distinct.updates.remove(sink) } func test_updates_subscribersMaySeeDifferentChanges() { let test = TestUpdatableValue(0) let distinct = test.distinct() let sink0to2 = MockValueUpdateSink() distinct.updates.add(sink0to2) let sink0to3 = MockValueUpdateSink() distinct.updates.add(sink0to3) sink0to2.expecting("begin") { sink0to3.expecting("begin") { test.beginTransaction() } } sink0to2.expectingNothing { sink0to3.expectingNothing { test.value = 1 } } let sink1to2 = MockValueUpdateSink() sink1to2.expecting("begin") { distinct.updates.add(sink1to2) } let sink1to3 = MockValueUpdateSink() sink1to3.expecting("begin") { distinct.updates.add(sink1to3) } sink0to2.expectingNothing { sink0to3.expectingNothing { sink1to2.expectingNothing { sink1to3.expectingNothing { test.value = 2 } } } } sink0to2.expecting(["0 -> 2", "end"]) { distinct.updates.remove(sink0to2) } sink1to2.expecting(["1 -> 2", "end"]) { distinct.updates.remove(sink1to2) } sink0to3.expectingNothing { sink1to3.expectingNothing { test.value = 3 } } sink0to3.expecting(["0 -> 3", "end"]) { sink1to3.expecting(["1 -> 3", "end"]) { test.endTransaction() } } sink0to3.expectingNothing { distinct.updates.remove(sink0to3) } sink1to3.expectingNothing { distinct.updates.remove(sink1to3) } } func test_values_defaultEqualityTest() { let test = TestObservableValue(0) let values = test.distinct().values let sink = MockSink() sink.expecting(0) { values.add(sink) } sink.expectingNothing { test.value = 0 } sink.expecting(1) { test.value = 1 } sink.expectingNothing { test.value = 1 test.value = 1 } sink.expecting(2) { test.value = 2 } values.remove(sink) } func test_futureValues_defaultEqualityTest() { let test = TestObservableValue(0) let values = test.distinct().futureValues let sink = MockSink() sink.expectingNothing { values.add(sink) test.value = 0 } sink.expecting(1) { test.value = 1 } sink.expectingNothing { test.value = 1 test.value = 1 } sink.expecting(2) { test.value = 2 } values.remove(sink) } func test_values_customEqualityTest() { let test = TestObservableValue(0) // This is a really stupid equality test: 1 is never equal to anything, while everything else is the same. // This will only let through changes from/to a 1 value. let distinct = test.distinct { a, b in a != 1 && b != 1 } let values = distinct.values let sink = MockSink() sink.expecting(0) { values.add(sink) } sink.expectingNothing { test.value = 0 } sink.expecting(1) { test.value = 1 } sink.expecting(1) { test.value = 1 } sink.expecting(1) { test.value = 1 } sink.expecting(2) { test.value = 2 } sink.expectingNothing { test.value = 2 test.value = 2 test.value = 3 test.value = 2 test.value = 4 } values.remove(sink) } func testDistinct_IsUpdatableWhenCalledOnUpdatables() { let test = TestUpdatableValue(0) let d = test.distinct() XCTAssertEqual(d.value, 0) let mock = MockValueUpdateSink(d) mock.expecting(["begin", "0 -> 42", "end"]) { d.value = 42 } XCTAssertEqual(d.value, 42) XCTAssertEqual(test.value, 42) mock.expecting(["begin", "42 -> 23", "end"]) { d.withTransaction { d.value = 23 } } mock.expecting(["begin", "end"]) { d.withTransaction {} } XCTAssertEqual(d.value, 23) XCTAssertEqual(test.value, 23) } } ================================================ FILE: Tests/GlueKitTests/DistinctUnionTests.swift ================================================ // // DistinctUnionTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-04. // Copyright © 2015–2017 Károly Lőrentey. // import Foundation import XCTest import GlueKit class DistinctUnionTests: XCTestCase { func test_getters() { let array: ArrayVariable = [0, 1, 2, 2, 3, 4, 5, 5, 5] let set = array.distinctUnion() XCTAssertTrue(set.isBuffered) XCTAssertEqual(set.count, 6) XCTAssertEqual(set.value, Set(0 ..< 6)) XCTAssertTrue(set.contains(0)) XCTAssertFalse(set.contains(7)) XCTAssertTrue(set.isSubset(of: Set(0 ..< 100))) XCTAssertTrue(set.isSubset(of: Set(0 ..< 6))) XCTAssertFalse(set.isSubset(of: Set(-5 ..< 5))) XCTAssertFalse(set.isSubset(of: Set(-5 ..< 0))) XCTAssertTrue(set.isSuperset(of: Set(0 ..< 4))) XCTAssertTrue(set.isSuperset(of: Set(0 ..< 6))) XCTAssertFalse(set.isSuperset(of: Set(-1 ..< 6))) XCTAssertFalse(set.isSuperset(of: Set(-1 ..< 3))) } func test_updates() { let array: ArrayVariable = [0] let set = array.distinctUnion() let mock = MockSetObserver(set) mock.expecting(["begin", "[]/[1]", "end"]) { array.append(1) } mock.expecting(["begin", "[]/[2]", "end"]) { array.append(2) } XCTAssertEqual(set.value, Set([0, 1, 2])) mock.expecting(["begin", "end"]) { array.append(1) } XCTAssertEqual(array.value, [0, 1, 2, 1]) XCTAssertEqual(set.value, Set([0, 1, 2])) mock.expecting(["begin", "end"]) { _ = array.remove(at: 3) } XCTAssertEqual(array.value, [0, 1, 2]) XCTAssertEqual(set.value, Set([0, 1, 2])) mock.expecting(["begin", "[1]/[]", "end"]) { _ = array.remove(at: 1) } XCTAssertEqual(array.value, [0, 2]) XCTAssertEqual(set.value, Set([0, 2])) mock.expecting(["begin", "[2]/[3]", "end"]) { array[1] = 3 } XCTAssertEqual(array.value, [0, 3]) XCTAssertEqual(set.value, Set([0, 3])) mock.expecting(["begin", "[]/[4, 5, 6]", "end"]) { array.append(contentsOf: [4, 4, 4, 5, 5, 6]) } XCTAssertEqual(array.value, [0, 3, 4, 4, 4, 5, 5, 6]) XCTAssertEqual(set.value, Set([0, 3, 4, 5, 6])) mock.expecting(["begin", "[6]/[]", "[4]/[]", "[0]/[]", "end"]) { // Remove even values array.withTransaction { for i in (0 ..< array.count).reversed() { if array[i] & 1 == 0 { array.remove(at: i) } } } } XCTAssertEqual(array.value, [3, 5, 5]) XCTAssertEqual(set.value, Set([3, 5])) mock.expecting(["begin", "[3, 5]/[]", "end"]) { array.removeAll() } XCTAssertEqual(set.value, Set()) } } ================================================ FILE: Tests/GlueKitTests/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 ================================================ FILE: Tests/GlueKitTests/KVOSupportTests.swift ================================================ // // KVOSupportTests.swift // GlueKit // // Created by Károly Lőrentey on 2015-12-03. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest import GlueKit private class Fixture: NSObject { var _name: String = "" var _count: Int = 0 var _optional: String? = nil var _next: Fixture? = nil @objc dynamic var name: String { get { return _name } set { _name = newValue } } @objc dynamic var count: Int { get { return _count } set { _count = newValue } } @objc dynamic var optional: String? { get { return _optional } set { _optional = newValue } } @objc dynamic var next: Fixture? { get { return _next } set { _next = newValue } } } private class RawKVOObserver: NSObject { let object: NSObject let keyPath: String let sink: (AnyObject) -> Void var observerContext: Int8 = 0 var observing: Bool init(object: NSObject, keyPath: String, sink: @escaping (AnyObject) -> Void) { self.object = object self.keyPath = keyPath self.sink = sink self.observing = true super.init() object.addObserver(self, forKeyPath: keyPath, options: .new, context: &self.observerContext) } deinit { disconnect() } func disconnect() { if observing { object.removeObserver(self, forKeyPath: keyPath, context: &self.observerContext) observing = false } } override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if context == &self.observerContext { let newValue = change![NSKeyValueChangeKey.newKey]! sink(newValue as AnyObject) } else { super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) } } } class KVOSupportTests: XCTestCase { func test_changes_BasicKVOWithIntegers() { let object = Fixture() let count = object.observable(for: \.count) var r = [Int]() let c = count.changes.subscribe { r.append($0.new) } object.count = 1 object.count = 2 object.count = 3 c.disconnect() XCTAssertEqual(r, [1, 2, 3]) } func test_changes_BasicKVOWithStrings() { let object = Fixture() var r = [String]() let c = object.observable(for: \.name).changes.subscribe { r.append($0.new) } object.name = "Alice" object.name = "Bob" object.name = "Charlie" c.disconnect() object.name = "Daniel" XCTAssertEqual(r, ["Alice", "Bob", "Charlie"]) } func test_changes_BasicKVOWithOptionals() { let object = Fixture() var r = [String?]() let c = object.observable(for: \.optional).changes.subscribe { r.append($0.new) } object.optional = "Alice" object.optional = nil object.optional = "Bob" object.optional = nil object.optional = nil c.disconnect() object.optional = "Daniel" object.optional = nil let expected: [String?] = ["Alice", nil, "Bob", nil, nil] XCTAssert(r.elementsEqual(expected, by: { $0 == $1 })) } func test_changes_DisconnectActuallyDisconnects() { let object = Fixture() let count = object.observable(for: \.count) var r = [Int]() let c = count.changes.subscribe { r.append($0.new) } object.count = 1 c.disconnect() object.count = 2 object.count = 3 XCTAssertEqual(r, [1]) } func test_changes_SourceRetainsObject() { var source: AnySource>? = nil weak var weakObject: NSObject? = nil do { let object = Fixture() weakObject = object source = object.observable(for: \.count).changes } XCTAssertNotNil(weakObject) source = nil XCTAssertNil(weakObject) noop(source) } func test_changes_ConnectionRetainsObject() { var c: Connection? = nil weak var weakObject: NSObject? = nil do { let object = Fixture() weakObject = object c = object.observable(for: \.count).changes.subscribe { _ in } } XCTAssertNotNil(weakObject) c?.disconnect() XCTAssertNil(weakObject) } //MARK: Reentrant observers func test_rawKVO_ReentrantUpdates() { // KVO supports reentrant updates, but it performs them synchronously, always sending the most up to date value. let object = Fixture() var s = "" let observer = RawKVOObserver(object: object, keyPath: "count") { any in let i = (any as! NSNumber).intValue s += " (\(i)" if i > 0 { object.count = i - 1 } s += ")" } object.count = 3 // Note deeply nested invocations. XCTAssertEqual(s, " (3 (2 (1 (0))))") observer.disconnect() } func test_changes_ReentrantUpdates1() { // KVO supports reentrant updates, but it performs them synchronously, always sending the most up to date value. // Our observable delays sending changes until the transaction is over, let object = Fixture() let count = object.observable(for: \.count) var s = "" let c = count.updates.subscribe { update in switch update { case .beginTransaction: s += "(<)" case .change(let change): s += "(\(change))" if count.value > 0 { object.count = count.value - 1 } case .endTransaction: s += "(>)" } } object.count = 3 XCTAssertEqual(object.count, 0) // Contrast this with the previous test. XCTAssertEqual(s, "(<)(0 -> 3)(3 -> 2)(2 -> 1)(1 -> 0)(>)") c.disconnect() } func test_changes_ReentrantUpdates2() { // KVO supports reentrant updates, but it performs them synchronously, always sending the most up to date value. let object = Fixture() let count = object.observable(for: \.count) var s = "" let c = count.updates.subscribe { update in switch update { case .beginTransaction: s += "(<)" case .change(let change): s += "(\(change))" case .endTransaction: s += "(>)" if count.value > 0 { object.count = count.value - 1 } } } object.count = 3 XCTAssertEqual(object.count, 0) // Contrast this with the previous test. XCTAssertEqual(s, "(<)(0 -> 3)(>)(<)(3 -> 2)(>)(<)(2 -> 1)(>)(<)(1 -> 0)(>)") c.disconnect() } func test_rawKVO_MutuallyReentrantUpdates() { // KVO supports reentrant updates, but it performs them synchronously, always sending the most up to date value. let object = Fixture() var s = "" let observer1 = RawKVOObserver(object: object, keyPath: "count") { any in let i = (any as! NSNumber).intValue s += " (\(i)" if i > 0 { object.count = i - 1 } s += ")" } let observer2 = RawKVOObserver(object: object, keyPath: "count") { any in let i = (any as! NSNumber).intValue s += " (\(i)" if i > 0 { object.count = i - 1 } s += ")" } object.count = 2 // Note deeply nested invocations and how observers always receive the latest value (shortening the cascade). XCTAssertEqual(s, " (2 (1 (0) (0)) (0)) (0)") observer1.disconnect() observer2.disconnect() } func test_changes_MutuallyReentrantUpdates() { // KVO supports reentrant updates, but it performs them synchronously, always sending the most up to date value. // However, our changes source will delay notifications until the end of the transaction. let object = Fixture() let count = object.observable(for: \.count) var s = "" let c1 = count.changes.subscribe { c in let i = c.new s += " (\(c)" if i > 0 { object.count = i - 1 } s += ")" } let c2 = count.changes.subscribe { c in let i = c.new s += " (\(c)" if i > 0 { object.count = i - 1 } s += ")" } object.count = 2 // Contrast this with the previous test. XCTAssertEqual(s, " (0 -> 2 (2 -> 1 (1 -> 0))) (0 -> 0)") c1.disconnect() c2.disconnect() } func test_updates_WillChangeStartsATransaction() { let object = Fixture() let count = object.observable(for: \.count) let sink = MockValueUpdateSink(count) sink.expecting("begin") { object.willChangeValue(forKey: "count") } sink.expectingNothing { object._count += 1 } sink.expecting(["0 -> 1", "end"]) { object.didChangeValue(forKey: "count") } sink.disconnect() } func test_updates_WillChangeStartsATransaction2() { let object = Fixture() let count = object.observable(for: \.count) let sink = MockValueUpdateSink(count) sink.expecting("begin") { object.willChangeValue(forKey: "count") } sink.expectingNothing { object._count += 1 } sink.expectingNothing { object.willChangeValue(forKey: "count") } sink.expectingNothing { object._count += 1 } sink.expectingNothing() { object.didChangeValue(forKey: "count") } sink.expecting(["0 -> 2", "end"]) { object.didChangeValue(forKey: "count") } sink.disconnect() } func test_updates_SubscribingAfterWillChange() { let object = Fixture() object.willChangeValue(forKey: "count") let count = object.observable(for: \.count) let sink = MockValueUpdateSink() // The change that was pending at the time of subscription isn't reported. sink.expectingNothing { count.add(sink) object._count += 1 object.didChangeValue(forKey: "count") } sink.expecting("begin") { object.willChangeValue(forKey: "count") } sink.expectingNothing { object._count += 1 } sink.expecting(["1 -> 2", "end"]) { object.didChangeValue(forKey: "count") } count.remove(sink) } func test_updates_UnsubscribingBeforeDidChange() { let object = Fixture() let count = object.observable(for: \.count) let sink = MockValueUpdateSink() count.add(sink) sink.expecting("begin") { object.willChangeValue(forKey: "count") } sink.expectingNothing { object._count += 1 } // We get "end" due to TransactionState's bracketing, but the change itself isn't reported. sink.expecting("end") { count.remove(sink) } sink.expectingNothing { object.didChangeValue(forKey: "count") } withExtendedLifetime(count) {} } func test_updatable_IntegerKey() { let object = Fixture() let count = object.updatable(for: \.count) let sink = MockValueUpdateSink(count) sink.expecting(["begin", "0 -> 1", "end"]) { count.value = 1 } // Our KVO-adaptor updatables behave as if they were buffered sink.expecting("begin") { count.apply(.beginTransaction) } sink.expectingNothing { count.apply(ValueChange(from: 1, to: 2)) count.apply(ValueChange(from: 2, to: 3)) } sink.expecting(["1 -> 3", "end"]) { count.apply(.endTransaction) } // will/didChange gets translated into begin/endTransaction sink.expecting("begin") { object.willChangeValue(forKey: "count") } sink.expectingNothing { object._count = 4 } sink.expecting(["3 -> 4", "end"]) { object.didChangeValue(forKey: "count") } sink.disconnect() } func test_updatable_OptionalKey() { let object = Fixture() let updatable = object.updatable(for: \.optional) let sink = MockValueUpdateSink(updatable) sink.expecting(["begin", "nil -> Optional(\"foo\")", "end"]) { updatable.value = "foo" } // Our KVO-adaptor updatables behave as if they were buffered sink.expecting("begin") { updatable.apply(.beginTransaction) } sink.expectingNothing { updatable.apply(ValueChange(from: "foo", to: nil)) updatable.apply(ValueChange(from: nil, to: "bar")) } sink.expecting(["Optional(\"foo\") -> Optional(\"bar\")", "end"]) { updatable.apply(.endTransaction) } sink.disconnect() } func test_observable_keyPath() { let object = Fixture() let next = Fixture() object.next = next // let count = object.observable(for: \.next?.count) // // let sink = MockValueUpdateSink(count) // // sink.expecting(["begin", "0 -> 1", "end"]) { // next.count = 1 // } // // sink.expecting(["begin", "1 -> 2", "end"]) { // object.setValue(2, forKeyPath: "next.count") // } // // sink.expecting("begin") { // next.willChangeValue(forKey: "count") // } // sink.expectingNothing { // next._count = 3 // } // sink.expecting(["2 -> 3", "end"]) { // next.didChangeValue(forKey: "count") // } // // let next2 = Fixture() // next2.count = 4 // // sink.expecting("begin") { // object.willChangeValue(forKey: "next") // } // sink.expectingNothing { // object._next = next2 // } // sink.expecting(["3 -> 4", "end"]) { // object.didChangeValue(forKey: "next") // } // // sink.disconnect() } func test_observable_keyPathNestedTransactions() { // let object = Fixture() // let next = Fixture() // next.count = 1 // // object.next = next // // let next2 = Fixture() // next2.count = 2 // // let count = object.observable(for: \.next?.count) // // let sink = MockValueUpdateSink(count) // // sink.expecting("begin") { // object.willChangeValue(forKey: "next") // } // sink.expectingNothing { // next2.willChangeValue(forKey: "count") // object._next = next2 // next2._count = 3 // } // // sink.expecting(["1 -> 3", "end"]) { // object.didChangeValue(forKey: "next") // } // sink.expectingNothing { // next2._count = 4 // Unfortunately, this change never gets reported. // next2.didChangeValue(forKey: "count") // } // // sink.disconnect() } } ================================================ FILE: Tests/GlueKitTests/MergedSourceTests.swift ================================================ // // MergedSourceTests.swift // GlueKit // // Created by Károly Lőrentey on 2015-12-04. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest import GlueKit class MergedSourceTests: XCTestCase { func testSimpleMerge() { let s1 = Signal() let s2 = Signal() let source = s1.merged(with: s2) let sink = MockSink() source.add(sink) sink.expecting(11) { s1.send(11) } sink.expecting(21) { s2.send(21) } sink.expecting(12) { s1.send(12) } sink.expecting(13) { s1.send(13) } sink.expecting(22) { s2.send(22) } sink.expecting(23) { s2.send(23) } source.remove(sink) } func testNaryMerge() { var signals: [Signal] = [] (0 ..< 10).forEach { _ in signals.append(Signal()) } let merge = Signal.merge(signals) let sink = MockSink() merge.add(sink) sink.expecting(Array(0 ..< 20)) { for i in 0 ..< 20 { signals[i % signals.count].send(i) } } merge.remove(sink) } func testReentrantMerge() { let s1 = Signal() let s2 = Signal() let source = s1.merged(with: s2) var s = "" let c = source.subscribe { i in s += " (\(i)" if i > 0 { s2.send(i - 1) } s += ")" } s1.send(3) XCTAssertEqual(s, " (3) (2) (1) (0)") c.disconnect() } func testVariadicMerge() { let s1 = Signal() let s2 = Signal() let s3 = Signal() let s4 = Signal() let merge = Signal.merge(s1, s2, s3, s4) let sink = MockSink() merge.add(sink) sink.expecting(1) { s1.send(1) } sink.expecting(2) { s1.send(2) } sink.expecting(3) { s1.send(3) } sink.expecting(4) { s1.send(4) } merge.remove(sink) } func testMergeChaining() { let s1 = Signal() let s2 = Signal() let s3 = Signal() let s4 = Signal() // This does not chain three merged sources together; it creates a single merged source containing all sources. let merge = s1.merged(with: s2).merged(with: s3).merged(with: s4) let sink = MockSink() merge.add(sink) sink.expecting(1) { s1.send(1) } sink.expecting(2) { s1.send(2) } sink.expecting(3) { s1.send(3) } sink.expecting(4) { s1.send(4) } merge.remove(sink) } } ================================================ FILE: Tests/GlueKitTests/MockArrayObserver.swift ================================================ // // MockArrayObserver.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-06. // Copyright © 2015–2017 Károly Lőrentey. // import Foundation import XCTest import GlueKit internal func describe(_ update: Update>?) -> String { guard let update = update else { return "nil" } switch update { case .beginTransaction: return "begin" case .change(let change): let mods = change.modifications.map { mod in "\(mod)" } return "\(change.initialCount)\(mods.joined())" case .endTransaction: return "end" } } class MockArrayObserver: MockSinkProtocol { typealias Change = ArrayChange let state: MockSinkState, String> init() { state = .init({ describe($0) }) } init(_ source: Source) where Source.Value == Update { state = .init({ describe($0) }) self.subscribe(to: source) } convenience init(_ observable: Observable) where Observable.Change == Change { self.init(observable.updates) } func receive(_ value: Update) { state.receive(value) } } ================================================ FILE: Tests/GlueKitTests/MockSetObserver.swift ================================================ // // MockSetObserver.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-06. // Copyright © 2015–2017 Károly Lőrentey. // import Foundation import XCTest import GlueKit func describe(_ update: SetUpdate) -> String { switch update { case .beginTransaction: return "begin" case .change(let change): let removed = change.removed.sorted().map { "\($0)" }.joined(separator: ", ") let inserted = change.inserted.sorted().map { "\($0)" }.joined(separator: ", ") return "[\(removed)]/[\(inserted)]" case .endTransaction: return "end" } } class MockSetObserver: MockSinkProtocol { typealias Change = SetChange let state: MockSinkState, String> init() { state = .init({ describe($0) }) } init(_ source: Source) where Source.Value == Update { state = .init({ describe($0) }) self.subscribe(to: source) } convenience init(_ observable: Observable) where Observable.Change == Change { self.init(observable.updates) } func receive(_ value: Update) { state.receive(value) } } ================================================ FILE: Tests/GlueKitTests/MockSink.swift ================================================ // // MockSink.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-10. // Copyright © 2015–2017 Károly Lőrentey. // import Foundation import XCTest import GlueKit class MockSinkState { let transform: (Value) -> Output var isExpecting = false var expected: [[Output]] = [] var actual: [Output] = [] var connection: Connection? init(_ transform: @escaping (Value) -> Output) { self.connection = nil self.transform = transform } init(_ connection: Connection, _ transform: @escaping (Value) -> Output) { self.connection = connection self.transform = transform } deinit { connection?.disconnect() } func receive(_ input: Value) { if !isExpecting { XCTFail("Sink received unexpected value: \(input)") } else { actual.append(transform(input)) } } func run(file: StaticString, line: UInt, _ body: () throws -> R) rethrows -> R { isExpecting = true defer { switch expected.count { case 0: XCTAssertEqual(actual, [], file: file, line: line) case 1: XCTAssertEqual(actual, expected[0], file: file, line: line) default: XCTAssertTrue(expected.contains(where: { actual == $0 }), "Unexpected values received: \(actual)", file: file, line: line) } actual = [] expected = [] isExpecting = false } return try body() } func disconnect() { connection?.disconnect() connection = nil } } protocol MockSinkProtocol: class, SinkType { associatedtype Output: Equatable var state: MockSinkState { get } } extension MockSinkProtocol { @discardableResult func expectingNothing(file: StaticString = #file, line: UInt = #line, body: () throws -> R) rethrows -> R { precondition(state.expected.isEmpty) return try state.run(file: file, line: line, body) } @discardableResult func expecting(_ value: Output, file: StaticString = #file, line: UInt = #line, body: () throws -> R) rethrows -> R { precondition(state.expected.isEmpty) state.expected = [[value]] return try state.run(file: file, line: line, body) } @discardableResult func expecting(_ values: [Output], file: StaticString = #file, line: UInt = #line, body: () throws -> R) rethrows -> R { precondition(state.expected.isEmpty) state.expected = [values] return try state.run(file: file, line: line, body) } @discardableResult func expectingOneOf(_ values: [[Output]], file: StaticString = #file, line: UInt = #line, body: () throws -> R) rethrows -> R { precondition(state.expected.isEmpty) state.expected = values return try state.run(file: file, line: line, body) } func subscribe(to source: Source) where Source.Value == Value { precondition(state.connection == nil) state.connection = source.subscribe { [unowned self] (input: Value) -> Void in self.receive(input) } } func disconnect() { state.disconnect() } } class TransformedMockSink: MockSinkProtocol { let state: MockSinkState init(_ transform: @escaping (Value) -> Output) { self.state = .init(transform) } init(_ source: Source, _ transform: @escaping (Value) -> Output) where Source.Value == Value { self.state = .init(transform) self.subscribe(to: source) } func receive(_ input: Value) { state.receive(input) } } class MockSink: MockSinkProtocol { let state: MockSinkState init() { self.state = .init({ $0 }) } init(_ source: Source) where Source.Value == Value { self.state = .init({ $0 }) self.subscribe(to: source) } func receive(_ input: Value) { state.receive(input) } } ================================================ FILE: Tests/GlueKitTests/MockUpdateSink.swift ================================================ // // MockUpdateSink.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-09. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest import GlueKit internal func describe(_ update: Update?) -> String { guard let update = update else { return "nil" } switch update { case .beginTransaction: return "begin" case .change(let change): return "\(change)" case .endTransaction: return "end" } } class MockUpdateSink: MockSinkProtocol { let state: MockSinkState, String> init() { state = .init({ describe($0) }) } init(_ source: Source) where Source.Value == Update { state = .init({ describe($0) }) self.subscribe(to: source) } convenience init(_ observable: Observable) where Observable.Change == Change { self.init(observable.updates) } func receive(_ value: Update) { state.receive(value) } } typealias MockValueUpdateSink = MockUpdateSink> ================================================ FILE: Tests/GlueKitTests/NSUserDefaultsSupportTests.swift ================================================ // // NSUserDefaultsSupportTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-11-02. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest extension UserDefaults { var testValue: Bool { get { return self.bool(forKey: "TestKey") } set { self.set(newValue, forKey: "TestKey") } } } class NSUserDefaultsSupportTests: XCTestCase { let key = "TestKey" let defaults = UserDefaults.standard var context: UInt8 = 0 var notifications: [[NSKeyValueChangeKey: Any]] = [] override func setUp() { super.setUp() defaults.removeObject(forKey: key) } override func tearDown() { defaults.removeObject(forKey: key) super.tearDown() } func testStandardNotifications() { defaults.addObserver(self, forKeyPath: key, options: [.old, .new], context: &context) defaults.set(true, forKey: key) defaults.removeObserver(self, forKeyPath: key, context: &context) XCTAssertEqual(notifications.count, 1, "Unexpected notifications: \(notifications)") } override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if context == &self.context { print(change ?? "nil") notifications.append(change!) } else { super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) } } func testAny() { let updatable = defaults.glue.updatable(forKey: key) XCTAssertNil(updatable.value) updatable.value = 42 XCTAssertEqual(updatable.value as? Int, 42) XCTAssertEqual(defaults.integer(forKey: key), 42) updatable.value = "Foobar" XCTAssertEqual(updatable.value as? String, "Foobar") XCTAssertEqual(defaults.string(forKey: key), "Foobar") let sink = MockValueUpdateSink(updatable) sink.expecting(["begin", "Optional(Foobar) -> Optional(23.125)", "end"]) { updatable.value = 23.125 } sink.expecting(["begin", "Optional(23.125) -> Optional(Barney)", "end"]) { defaults.set("Barney", forKey: key) } sink.disconnect() } func testBool() { let updatable = defaults.glue.updatable(forKey: key, defaultValue: false) XCTAssertFalse(updatable.value) updatable.value = true XCTAssertEqual(updatable.value, true) defaults.set(1, forKey: key) XCTAssertEqual(updatable.value, true) defaults.set(0, forKey: key) XCTAssertEqual(updatable.value, false) defaults.set(1.0, forKey: key) XCTAssertEqual(updatable.value, true) defaults.set("YES", forKey: key) XCTAssertEqual(updatable.value, false) let sink = MockValueUpdateSink(updatable) sink.expecting(["begin", "false -> true", "end"]) { updatable.value = true } sink.expecting(["begin", "true -> false", "end"]) { defaults.set(false, forKey: key) } sink.disconnect() } func testInt() { let updatable = defaults.glue.updatable(forKey: key, defaultValue: 0) XCTAssertEqual(updatable.value, 0) updatable.value = 1 XCTAssertEqual(updatable.value, 1) defaults.set(2, forKey: key) XCTAssertEqual(updatable.value, 2) defaults.set(nil, forKey: key) XCTAssertEqual(updatable.value, 0) defaults.set(42.0, forKey: key) XCTAssertEqual(updatable.value, 42) defaults.set(true, forKey: key) XCTAssertEqual(updatable.value, 0) // kCFBooleanTrue is not directly convertible to Int defaults.set(42.5, forKey: key) XCTAssertEqual(updatable.value, 42) defaults.set("23", forKey: key) XCTAssertEqual(updatable.value, 0) let sink = MockValueUpdateSink(updatable) sink.expecting(["begin", "0 -> 3", "end"]) { updatable.value = 3 } sink.expecting(["begin", "3 -> 4", "end"]) { defaults.set(4, forKey: key) } sink.disconnect() } func testDouble() { let updatable = defaults.glue.updatable(forKey: key, defaultValue: 0.0) XCTAssertEqual(updatable.value, 0) updatable.value = 1 XCTAssertEqual(updatable.value, 1) defaults.set(2, forKey: key) XCTAssertEqual(updatable.value, 2) defaults.set(nil, forKey: key) XCTAssertEqual(updatable.value, 0) defaults.set(42.0, forKey: key) XCTAssertEqual(updatable.value, 42) defaults.set(true, forKey: key) XCTAssertEqual(updatable.value, 0.0) // kCFBooleanTrue is not directly convertible to Int defaults.set(42.5, forKey: key) XCTAssertEqual(updatable.value, 42.5) defaults.set("23", forKey: key) XCTAssertEqual(updatable.value, 0) let sink = MockValueUpdateSink(updatable) sink.expecting(["begin", "0.0 -> 3.0", "end"]) { updatable.value = 3 } sink.expecting(["begin", "3.0 -> 4.0", "end"]) { defaults.set(4, forKey: key) } sink.disconnect() } func testString() { let updatable = defaults.glue.updatable(forKey: key, as: (String?).self) XCTAssertEqual(updatable.value, nil) updatable.value = "Foo" XCTAssertEqual(updatable.value, "Foo") defaults.set(2, forKey: key) XCTAssertEqual(updatable.value, nil) defaults.set(nil, forKey: key) XCTAssertEqual(updatable.value, nil) defaults.set(42.0, forKey: key) XCTAssertEqual(updatable.value, nil) defaults.set(true, forKey: key) XCTAssertEqual(updatable.value, nil) defaults.set(42.5, forKey: key) XCTAssertEqual(updatable.value, nil) defaults.set("23", forKey: key) XCTAssertEqual(updatable.value, "23") let sink = MockValueUpdateSink(updatable) sink.expecting(["begin", "Optional(\"23\") -> Optional(\"Fred\")", "end"]) { updatable.value = "Fred" } sink.expecting(["begin", "Optional(\"Fred\") -> Optional(\"Barney\")", "end"]) { defaults.set("Barney", forKey: key) } sink.disconnect() } } ================================================ FILE: Tests/GlueKitTests/NotificationCenterSupportTests.swift ================================================ // // NotificationCenterSupportTests.swift // GlueKit // // Created by Károly Lőrentey on 2015-12-04. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest private let center = NotificationCenter.default private func post(_ value: Int) { post("TestNotification", value) } private func post(_ name: String, _ value: Int) { center.post(name: Notification.Name(rawValue: name), object: nil, userInfo: ["Value": value]) } let testNotification = NSNotification.Name("TestNotification") class NotificationCenterSupportTests: XCTestCase { func testSimpleNotification() { var r = [Int]() post(1) let c = center.glue.source(forName: testNotification).subscribe { notification in r.append((notification as NSNotification).userInfo!["Value"] as! Int) } post(2) post(3) c.disconnect() post(4) XCTAssertEqual(r, [2, 3]) } func testReentrancyInRawNotificationCenter() { // The notification center supports reentrancy but it is synchronous, just like KVO - except it doesn't force "latest" values var s = "" let observer = center.addObserver(forName: testNotification, object: nil, queue: nil) { notification in let value = (notification as NSNotification).userInfo!["Value"] as! Int s += " (\(value)" if value > 0 { post(value - 1) } s += ")" } post(3) XCTAssertEqual(s, " (3 (2 (1 (0))))") center.removeObserver(observer) } func testReentrancyInGlueKit() { var s = "" let c = center.glue.source(forName: testNotification).subscribe { notification in let value = (notification as NSNotification).userInfo!["Value"] as! Int s += " (\(value)" if value > 0 { post(value - 1) } s += ")" } post(3) // Nicely serialized invocations. XCTAssertEqual(s, " (3) (2) (1) (0)") c.disconnect() } func testReentrancyCascadeInRawNotificationCenter() { // The notification center supports reentrancy but it is synchronous, just like KVO - except it doesn't force "latest" values var firstIndex: Int? = nil var receivedValues: [[Int]] = [[], []] var s = "" let block: (Int) -> (Notification) -> Void = { index in return { notification in if firstIndex == nil { firstIndex = index } let value = (notification as NSNotification).userInfo!["Value"] as! Int receivedValues[index].append(value) s += " (\(value)" if value > 0 { post(value - 1) } s += ")" } } let observer1 = center.addObserver(forName: testNotification, object: nil, queue: nil, using: block(0)) let observer2 = center.addObserver(forName: testNotification, object: nil, queue: nil, using: block(1)) post(2) // Note nested invocations and strange ordering of delivered values. XCTAssertEqual(receivedValues[firstIndex!], [2, 1, 0, 0, 1, 0, 0]) XCTAssertEqual(receivedValues[1 - firstIndex!], [0, 1, 0, 2, 0, 1, 0]) XCTAssertEqual(s, " (2 (1 (0) (0)) (1 (0) (0))) (2 (1 (0) (0)) (1 (0) (0)))") center.removeObserver(observer1) center.removeObserver(observer2) } func testReentrancyCascadeInGlueKit() { var firstIndex: Int? = nil var receivedValues: [[Int]] = [[], []] var s = "" let block: (Int) -> (Notification) -> Void = { index in return { notification in if firstIndex == nil { firstIndex = index } let value = (notification as NSNotification).userInfo!["Value"] as! Int receivedValues[index].append(value) s += " (\(value)" if value > 0 { post(value - 1) } s += ")" } } let source = center.glue.source(forName: testNotification) let c1 = source.subscribe(block(0)) let c2 = source.subscribe(block(1)) post(2) // Nicely serialized invocations. Values are progressing monotonically and there are no nested calls. // Note though that this wouldn't happen if the source wasn't shared above! XCTAssertEqual(receivedValues[0], [2, 1, 1, 0, 0, 0, 0]) XCTAssertEqual(receivedValues[1], [2, 1, 1, 0, 0, 0, 0]) XCTAssertEqual(s, " (2) (2) (1) (1) (1) (1) (0) (0) (0) (0) (0) (0) (0) (0)") c1.disconnect() c2.disconnect() } } ================================================ FILE: Tests/GlueKitTests/ObservableArrayTests.swift ================================================ // // ObservableArrayTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-10. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest @testable import GlueKit class TestObservableArray: ObservableArrayType, TransactionalThing { typealias Change = ArrayChange var _signal: TransactionalSignal>? = nil var _transactionCount: Int = 0 var _value: [Element] init(_ value: [Element]) { self._value = value } var count: Int { return _value.count } subscript(bounds: Range) -> ArraySlice { return _value[bounds] } func add(_ sink: Sink) where Sink.Value == Update> { signal.add(sink) } @discardableResult func remove(_ sink: Sink) -> Sink where Sink.Value == Update> { return signal.remove(sink) } func apply(_ change: ArrayChange) { if change.isEmpty { return } beginTransaction() _value.apply(change) sendChange(change) endTransaction() } } private class TestUpdatableArray: TestObservableArray, UpdatableArrayType { var value: [Element] { get { return super.value } set { self.apply(ArrayChange(from: value, to: newValue)) } } override subscript(bounds: Range) -> ArraySlice { get { return _value[bounds] } set { self.apply(ArrayChange(initialCount: count, modification: .replaceSlice(Array(self[bounds]), at: bounds.lowerBound, with: Array(newValue)))) } } subscript(index: Int) -> Element { get { return _value[index] } set { self.apply(ArrayChange(initialCount: count, modification: .replace(self[index], at: index, with: newValue))) } } func withTransaction(_ body: () -> Result) -> Result { beginTransaction() defer { endTransaction() } return body() } func apply(_ update: Update>) { switch update { case .beginTransaction: beginTransaction() case .change(let change): _value.apply(change) sendChange(change) case .endTransaction: endTransaction() } } } class ObservableArrayTests: XCTestCase { func testDefaultImplementations() { func check(isBuffered: Bool = false, make: ([Int]) -> T, convert: (T) -> A, apply: @escaping (T, ArrayChange) -> Void) where A.Element == Int { let t = make([1, 2, 3]) let test = convert(t) XCTAssertEqual(test.isBuffered, isBuffered) XCTAssertEqual(test.value, [1, 2, 3]) XCTAssertEqual(test.count, 3) XCTAssertEqual(test[0], 1) XCTAssertEqual(test[1], 2) XCTAssertEqual(test[2], 3) XCTAssertFalse(test.isEmpty) XCTAssertEqual(test.first, 1) XCTAssertEqual(test.last, 3) let observable = test.anyObservableValue let observableCount = test.observableCount XCTAssertEqual(observable.value, [1, 2, 3]) XCTAssertEqual(observableCount.value, 3) let mock = MockArrayObserver(test) let valueMock = MockValueUpdateSink(observable.map { "\($0)" }) // map is to convert array into something equatable let countMock = MockValueUpdateSink(observableCount) mock.expecting(["begin", "3.insert(4, at: 2)", "end"]) { valueMock.expecting(["begin", "[1, 2, 3] -> [1, 2, 4, 3]", "end"]) { countMock.expecting(["begin", "3 -> 4", "end"]) { apply(t, ArrayChange(initialCount: 3, modification: .insert(4, at: 2))) } } } XCTAssertEqual(test.value, [1, 2, 4, 3]) XCTAssertEqual(observable.value, [1, 2, 4, 3]) XCTAssertEqual(observableCount.value, 4) mock.expecting(["begin", "4.replaceSlice([1, 2, 4, 4], at: 0, with: [])", "end"]) { valueMock.expecting(["begin", "[1, 2, 4, 3] -> []", "end"]) { countMock.expecting(["begin", "4 -> 0", "end"]) { apply(t, ArrayChange(initialCount: 4, modification: .replaceSlice([1, 2, 4, 4], at: 0, with: []))) } } } XCTAssertEqual(test.value, []) XCTAssertEqual(observable.value, []) XCTAssertEqual(observableCount.value, 0) XCTAssertEqual(test.count, 0) XCTAssertTrue(test.isEmpty) XCTAssertEqual(test.first, nil) XCTAssertEqual(test.last, nil) } check(make: { TestObservableArray($0) }, convert: { $0 }, apply: { $0.apply($1) }) check(make: { TestObservableArray($0) }, convert: { $0.anyObservableArray }, apply: { $0.apply($1) }) check(make: { TestUpdatableArray($0) }, convert: { $0 }, apply: { $0.apply($1) }) check(make: { TestUpdatableArray($0) }, convert: { $0.anyObservableArray }, apply: { $0.apply($1) }) check(make: { TestUpdatableArray($0) }, convert: { $0.anyUpdatableArray }, apply: { $0.apply($1) }) check(make: { TestUpdatableArray($0) }, convert: { $0.anyUpdatableArray.anyObservableArray }, apply: { $0.apply($1) }) check(isBuffered: true, make: { ArrayVariable($0) }, convert: { $0 }, apply: { $0.apply($1) }) check(isBuffered: true, make: { ArrayVariable($0) }, convert: { $0.anyObservableArray }, apply: { $0.apply($1) }) check(isBuffered: true, make: { ArrayVariable($0) }, convert: { $0.anyUpdatableArray }, apply: { $0.apply($1) }) check(isBuffered: true, make: { ArrayVariable($0) }, convert: { $0.anyUpdatableArray.anyObservableArray }, apply: { $0.apply($1) }) check(isBuffered: true, make: { TestObservableArray($0) }, convert: { $0.buffered() }, apply: { $0.apply($1) }) check(isBuffered: true, make: { ArrayVariable($0) }, convert: { $0.buffered() }, apply: { $0.apply($1) }) } func testConstant() { let test = AnyObservableArray.constant([1, 2, 3]) XCTAssertTrue(test.isBuffered) XCTAssertEqual(test.count, 3) XCTAssertEqual(test.value, [1, 2, 3]) XCTAssertEqual(test[0], 1) XCTAssertEqual(test[1], 2) XCTAssertEqual(test[2], 3) XCTAssertEqual(test[0 ..< 2], [1, 2]) let mock = MockArrayObserver(test) mock.expectingNothing { // Whatevs } XCTAssertEqual(test.observableCount.value, 3) XCTAssertEqual(test.anyObservableValue.value, [1, 2, 3]) } func testUpdatable() { func check(make: ([Int]) -> A) where A.Element == Int { let test = make([1, 2, 3]) let mock = MockArrayObserver(test) mock.expectingNothing { test.apply(ArrayChange(initialCount: test.count)) } mock.expecting(["begin", "3.remove(1, at: 0).insert(4, at: 1)", "end"]) { var change = ArrayChange(initialCount: 3) change.add(.insert(4, at: 2)) change.add(.remove(1, at: 0)) test.apply(change) } XCTAssertEqual(test.value, [2, 4, 3]) mock.expecting(["begin", "3.replaceSlice([2, 4, 3], at: 0, with: [-1, -2, -3])", "end"]) { test.value = [-1, -2, -3] } XCTAssertEqual(test.value, [-1, -2, -3]) mock.expecting(["begin", "3.replace(-2, at: 1, with: 2)", "end"]) { test[1] = 2 } XCTAssertEqual(test.value, [-1, 2, -3]) mock.expecting(["begin", "3.replaceSlice([-1, 2], at: 0, with: [1, 2])", "end"]) { test[0 ..< 2] = [1, 2] } XCTAssertEqual(test.value, [1, 2, -3]) let updatable = test.anyUpdatableValue XCTAssertEqual(updatable.value, [1, 2, -3]) let umock = MockValueUpdateSink(updatable.map { "\($0)" }) // The mapping transforms the array into something equatable mock.expecting(["begin", "3.replaceSlice([1, 2, -3], at: 0, with: [0, 1, 2, 3])", "end"]) { umock.expecting(["begin", "[1, 2, -3] -> [0, 1, 2, 3]", "end"]) { updatable.value = [0, 1, 2, 3] } } XCTAssertEqual(updatable.value, [0, 1, 2, 3]) XCTAssertEqual(test.value, [0, 1, 2, 3]) umock.disconnect() mock.expecting(["begin", "4.remove(2, at: 2)", "3.insert(10, at: 1)", "end"]) { test.withTransaction { test.remove(at: 2) test.insert(10, at: 1) } } XCTAssertEqual(test.value, [0, 10, 1, 3]) mock.expecting(["begin", "4.replaceSlice([1, 3], at: 2, with: [11, 12, 13])", "end"]) { test.replaceSubrange(2 ..< 4, with: 11 ... 13) } XCTAssertEqual(test.value, [0, 10, 11, 12, 13]) mock.expecting(["begin", "5.replaceSlice([0], at: 0, with: [8, 9])", "end"]) { test.replaceSubrange(0 ..< 1, with: [8, 9]) } XCTAssertEqual(test.value, [8, 9, 10, 11, 12, 13]) mock.expecting(["begin", "6.insert(14, at: 6)", "end"]) { test.append(14) } XCTAssertEqual(test.value, [8, 9, 10, 11, 12, 13, 14]) mock.expecting(["begin", "7.replaceSlice([], at: 7, with: [15, 16, 17])", "end"]) { test.append(contentsOf: 15 ... 17) } XCTAssertEqual(test.value, [8, 9, 10, 11, 12, 13, 14, 15, 16, 17]) mock.expecting(["begin", "10.insert(20, at: 3)", "end"]) { test.insert(20, at: 3) } XCTAssertEqual(test.value, [8, 9, 10, 20, 11, 12, 13, 14, 15, 16, 17]) mock.expecting(["begin", "11.replaceSlice([], at: 4, with: [21, 22, 23])", "end"]) { test.insert(contentsOf: 21 ... 23, at: 4) } XCTAssertEqual(test.value, [8, 9, 10, 20, 21, 22, 23, 11, 12, 13, 14, 15, 16, 17]) mock.expecting(["begin", "14.remove(21, at: 4)", "end"]) { XCTAssertEqual(test.remove(at: 4), 21) } XCTAssertEqual(test.value, [8, 9, 10, 20, 22, 23, 11, 12, 13, 14, 15, 16, 17]) mock.expecting(["begin", "13.replaceSlice([20, 22, 23], at: 3, with: [])", "end"]) { test.removeSubrange(3 ..< 6) } XCTAssertEqual(test.value, [8, 9, 10, 11, 12, 13, 14, 15, 16, 17]) mock.expecting(["begin", "10.remove(8, at: 0)", "end"]) { XCTAssertEqual(test.removeFirst(), 8) } XCTAssertEqual(test.value, [9, 10, 11, 12, 13, 14, 15, 16, 17]) mock.expecting(["begin", "9.replaceSlice([9, 10], at: 0, with: [])", "end"]) { test.removeFirst(2) } XCTAssertEqual(test.value, [11, 12, 13, 14, 15, 16, 17]) mock.expecting(["begin", "7.remove(17, at: 6)", "end"]) { XCTAssertEqual(test.removeLast(), 17) } XCTAssertEqual(test.value, [11, 12, 13, 14, 15, 16]) mock.expecting(["begin", "6.replaceSlice([14, 15, 16], at: 3, with: [])", "end"]) { test.removeLast(3) } XCTAssertEqual(test.value, [11, 12, 13]) mock.expecting(["begin", "3.remove(13, at: 2)", "2.replace(12, at: 1, with: 20)", "end"]) { test.withTransaction { test.remove(at: 2) test[1] = 20 } } mock.expecting(["begin", "2.replaceSlice([11, 20], at: 0, with: [])", "end"]) { test.removeAll() } XCTAssertEqual(test.value, []) } check { TestUpdatableArray($0) } check { TestUpdatableArray($0).anyUpdatableArray } check { TestUpdatableArray($0).anyUpdatableArray.anyUpdatableArray } check { ArrayVariable($0) } check { ArrayVariable($0).anyUpdatableArray } } } ================================================ FILE: Tests/GlueKitTests/ObservableSetTests.swift ================================================ // // ObservableSetTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-10. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest @testable import GlueKit class TestObservableSet: _AbstractObservableSet, TransactionalThing { var _signal: TransactionalSignal>? = nil var _transactionCount: Int = 0 private var _value: Set init(_ value: Set) { self._value = value } override var value: Set { return _value } override func add(_ sink: Sink) where Sink.Value == Update { signal.add(sink) } @discardableResult override func remove(_ sink: Sink) -> Sink where Sink.Value == Update { return signal.remove(sink) } func apply(_ change: Change) { if change.isEmpty { return } beginTransaction() _value.subtract(change.removed) _value.formUnion(change.inserted) sendChange(change) endTransaction() } } private class TestObservableSet2: ObservableSetType, TransactionalThing { typealias Change = SetChange var _signal: TransactionalSignal>? = nil var _transactionCount: Int = 0 var _value: Set init(_ value: Set) { self._value = value } var value: Set { return _value } func add(_ sink: Sink) where Sink.Value == Update { signal.add(sink) } @discardableResult func remove(_ sink: Sink) -> Sink where Sink.Value == Update { return signal.remove(sink) } func apply(_ change: Change) { if change.isEmpty { return } beginTransaction() _value.subtract(change.removed) _value.formUnion(change.inserted) sendChange(change) endTransaction() } } private class TestUpdatableSet: _AbstractUpdatableSet, TransactionalThing { var _signal: TransactionalSignal>? = nil var _transactionCount: Int = 0 var _value: Set init(_ value: Set) { self._value = value } override var value: Set { get { return _value } set { self.apply(SetChange(removed: _value, inserted: newValue)) } } override func add(_ sink: Sink) where Sink.Value == Update { signal.add(sink) } @discardableResult override func remove(_ sink: Sink) -> Sink where Sink.Value == Update { return signal.remove(sink) } override func apply(_ update: Update) { switch update { case .beginTransaction: beginTransaction() case .change(let change): _value.subtract(change.removed) _value.formUnion(change.inserted) sendChange(change) case .endTransaction: endTransaction() } } } private class TestUpdatableSet2: UpdatableSetType, TransactionalThing { typealias Change = SetChange var _signal: TransactionalSignal>? = nil var _transactionCount: Int = 0 var _value: Set init(_ value: Set) { self._value = value } var value: Set { get { return _value } set { self.apply(SetChange(removed: _value, inserted: newValue)) } } func add(_ sink: Sink) where Sink.Value == Update { signal.add(sink) } @discardableResult func remove(_ sink: Sink) -> Sink where Sink.Value == Update { return signal.remove(sink) } func withTransaction(_ body: () -> Result) -> Result { beginTransaction() defer { endTransaction() } return body() } func apply(_ update: Update) { switch update { case .beginTransaction: beginTransaction() case .change(let change): _value.subtract(change.removed) _value.formUnion(change.inserted) sendChange(change) case .endTransaction: endTransaction() } } } private func describe(_ update: ValueUpdate>) -> String { switch update { case .beginTransaction: return "begin" case .change(let change): let old = change.old.sorted() let new = change.new.sorted() return "\(old) -> \(new)" case .endTransaction: return "end" } } func checkObservableSet(isBuffered: Bool = false, make: (Set) -> T, convert: (T) -> S, apply: @escaping (T, SetChange) -> Void) where S.Element == Int { let t = make([1, 2, 3]) let test = convert(t) XCTAssertEqual(test.value, [1, 2, 3]) XCTAssertEqual(test.isBuffered, isBuffered) XCTAssertEqual(test.count, 3) XCTAssertFalse(test.isEmpty) XCTAssertTrue(test.contains(2)) XCTAssertFalse(test.contains(4)) XCTAssertTrue(test.isSubset(of: [1, 2, 3, 4])) XCTAssertFalse(test.isSubset(of: [1, 3, 4])) XCTAssertTrue(test.isSuperset(of: [1, 2])) XCTAssertFalse(test.isSuperset(of: [0, 1])) let observableValue = test.anyObservableValue let observableCount = test.observableCount XCTAssertEqual(observableValue.value, [1, 2, 3]) XCTAssertEqual(observableCount.value, 3) let mock = MockSetObserver(test) let vmock = TransformedMockSink>, String>(observableValue.updates, { describe($0) }) let cmock = MockValueUpdateSink(observableCount) mock.expecting(["begin", "[]/[4]", "end"]) { vmock.expecting(["begin", "[1, 2, 3] -> [1, 2, 3, 4]", "end"]) { cmock.expecting(["begin", "3 -> 4", "end"]) { apply(t, SetChange(inserted: [4])) } } } XCTAssertTrue(test.contains(4)) XCTAssertEqual(test.value, [1, 2, 3, 4]) mock.expectingNothing { vmock.expectingNothing { cmock.expectingNothing { apply(t, SetChange()) } } } XCTAssertEqual(test.value, [1, 2, 3, 4]) } func checkUpdatableSet(_ make: (Set) -> U) where U.Element == Int { do { let test = make([1, 2, 3]) XCTAssertEqual(test.value, [1, 2, 3]) let mock = MockSetObserver(test) mock.expecting(["begin", "[]/[4]", "end"]) { test.insert(4) } XCTAssertEqual(test.value, [1, 2, 3, 4]) mock.expectingNothing { test.insert(2) } XCTAssertEqual(test.value, [1, 2, 3, 4]) mock.expecting(["begin", "[2]/[]", "end"]) { test.remove(2) } XCTAssertEqual(test.value, [1, 3, 4]) mock.expectingNothing { test.remove(2) } mock.expecting(["begin", "[]/[0]", "[3]/[]", "end"]) { test.withTransaction { test.insert(0) test.remove(3) } } XCTAssertEqual(test.value, [0, 1, 4]) mock.expecting(["begin", "[0, 1, 4]/[10, 20, 30]", "end"]) { test.value = [10, 20, 30] } XCTAssertEqual(test.value, [10, 20, 30]) mock.expecting(["begin", "[10, 20, 30]/[]", "end"]) { test.removeAll() } XCTAssertEqual(test.value, []) mock.expectingNothing { test.removeAll() } XCTAssertEqual(test.value, []) mock.expecting(["begin", "[]/[1, 2, 3]", "end"]) { test.formUnion([1, 2, 3]) } XCTAssertEqual(test.value, [1, 2, 3]) mock.expecting(["begin", "[3]/[]", "end"]) { test.formIntersection([0, 1, 2, 6]) } XCTAssertEqual(test.value, [1, 2]) mock.expecting(["begin", "[2]/[3, 4]", "end"]) { test.formSymmetricDifference([2, 3, 4]) } XCTAssertEqual(test.value, [1, 3, 4]) mock.expecting(["begin", "[1, 4]/[]", "end"]) { test.subtract([1, 4, 5]) } XCTAssertEqual(test.value, [3]) let v = test.anyUpdatableValue XCTAssertEqual(v.value, [3]) mock.expecting(["begin", "[]/[1, 2]", "end"]) { v.value = [1, 2, 3] } } // Try again with no observers. do { let test = make([1, 2, 3]) XCTAssertEqual(test.value, [1, 2, 3]) test.insert(4) XCTAssertEqual(test.value, [1, 2, 3, 4]) test.insert(2) XCTAssertEqual(test.value, [1, 2, 3, 4]) test.remove(2) XCTAssertEqual(test.value, [1, 3, 4]) test.remove(2) test.withTransaction { test.insert(0) test.remove(3) } XCTAssertEqual(test.value, [0, 1, 4]) test.value = [10, 20, 30] XCTAssertEqual(test.value, [10, 20, 30]) test.removeAll() XCTAssertEqual(test.value, []) test.removeAll() XCTAssertEqual(test.value, []) test.formUnion([1, 2, 3]) XCTAssertEqual(test.value, [1, 2, 3]) test.formIntersection([0, 1, 2, 6]) XCTAssertEqual(test.value, [1, 2]) test.formSymmetricDifference([2, 3, 4]) XCTAssertEqual(test.value, [1, 3, 4]) test.subtract([1, 4, 5]) XCTAssertEqual(test.value, [3]) } } class ObservableSetTypeTests: XCTestCase { func testDefaultImplementations() { checkObservableSet(make: { TestObservableSet($0) }, convert: { $0 }, apply: { $0.apply($1) }) checkObservableSet(make: { TestObservableSet($0) }, convert: { $0.anyObservableSet }, apply: { $0.apply($1) }) checkObservableSet(make: { TestObservableSet2($0) }, convert: { $0 }, apply: { $0.apply($1) }) checkObservableSet(make: { TestObservableSet2($0) }, convert: { $0.anyObservableSet }, apply: { $0.apply($1) }) checkObservableSet(make: { TestUpdatableSet($0) }, convert: { $0 }, apply: { $0.apply($1) }) checkObservableSet(make: { TestUpdatableSet($0) }, convert: { $0.anyUpdatableSet }, apply: { $0.apply($1) }) checkObservableSet(make: { TestUpdatableSet($0) }, convert: { $0.anyObservableSet }, apply: { $0.apply($1) }) checkObservableSet(make: { TestUpdatableSet($0) }, convert: { $0.anyUpdatableSet.anyObservableSet }, apply: { $0.apply($1) }) checkObservableSet(make: { TestUpdatableSet2($0) }, convert: { $0 }, apply: { $0.apply($1) }) checkObservableSet(make: { TestUpdatableSet2($0) }, convert: { $0.anyUpdatableSet }, apply: { $0.apply($1) }) checkObservableSet(make: { TestUpdatableSet2($0) }, convert: { $0.anyObservableSet }, apply: { $0.apply($1) }) checkObservableSet(make: { TestUpdatableSet2($0) }, convert: { $0.anyUpdatableSet.anyObservableSet }, apply: { $0.apply($1) }) checkObservableSet(isBuffered: true, make: { SetVariable($0) }, convert: { $0 }, apply: { $0.apply($1) }) class T { let foo: TestUpdatableSet init(_ v: Set) { self.foo = .init(v) } } checkObservableSet(make: { Variable(T($0)).map { $0.foo } }, convert: { $0 }, apply: { $0.apply($1) }) } func testObservableSetConstant() { let constant = AnyObservableSet.constant([1, 2, 3]) XCTAssertTrue(constant.isBuffered) XCTAssertEqual(constant.count, 3) XCTAssertEqual(constant.value, [1, 2, 3]) XCTAssertTrue(constant.contains(2)) XCTAssertFalse(constant.contains(0)) XCTAssertTrue(constant.isSubset(of: [1, 2, 3, 4])) XCTAssertFalse(constant.isSubset(of: [1, 3, 4])) XCTAssertTrue(constant.isSuperset(of: [1, 2])) XCTAssertFalse(constant.isSuperset(of: [0, 2])) let mock = MockSetObserver(constant) mock.expectingNothing { // Well whatever } let observable = constant.anyObservableValue XCTAssertEqual(observable.value, [1, 2, 3]) let observableCount = constant.observableCount XCTAssertEqual(observableCount.value, 3) } func testUpdatableDefaultImplementations() { checkUpdatableSet { TestUpdatableSet($0) } checkUpdatableSet { TestUpdatableSet($0).anyUpdatableSet } checkUpdatableSet { TestUpdatableSet($0).anyUpdatableSet.anyUpdatableSet } checkUpdatableSet { TestUpdatableSet2($0) } checkUpdatableSet { TestUpdatableSet2($0).anyUpdatableSet } checkUpdatableSet { TestUpdatableSet2($0).anyUpdatableSet.anyUpdatableSet } checkUpdatableSet { SetVariable($0) } checkUpdatableSet { SetVariable($0).anyUpdatableSet } class T { let foo: TestUpdatableSet init(_ v: Set) { self.foo = .init(v) } } checkUpdatableSet { Variable(T($0)).map { $0.foo } } } func testObservableContains() { let test = SetVariable([1, 2, 3]) let containsTwo = test.observableContains(2) XCTAssertEqual(containsTwo.value, true) let mock = MockValueUpdateSink(containsTwo) mock.expecting(["begin", "end", "begin", "end"]) { test.insert(5) test.remove(1) } XCTAssertEqual(containsTwo.value, true) mock.expecting(["begin", "true -> false", "end"]) { test.remove(2) } XCTAssertEqual(containsTwo.value, false) mock.expecting(["begin", "false -> true", "end"]) { test.formUnion([2, 6]) } XCTAssertEqual(containsTwo.value, true) } } ================================================ FILE: Tests/GlueKitTests/ObservableTypeTests.swift ================================================ // // ObservableTypeTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-28. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest import GlueKit class ObservableTypeTests: XCTestCase { func test_UpdatableType_withTransaction() { let test = TestUpdatable(0) let sink = MockUpdateSink() test.updates.add(sink) sink.expecting(["begin", "end"]) { test.withTransaction {} } sink.expecting(["begin", "0 -> 1", "end"]) { test.withTransaction { test.apply(TestChange(from: 0, to: 1)) } } sink.expecting(["begin", "1 -> 2", "2 -> 3", "end"]) { test.withTransaction { test.apply(TestChange(from: 1, to: 2)) test.apply(TestChange(from: 2, to: 3)) } } sink.expecting(["begin", "3 -> 4", "end"]) { test.withTransaction { test.withTransaction { test.apply(TestChange(from: 3, to: 4)) } } } test.updates.remove(sink) } func test_UpdatableType_applyChange() { let test = TestUpdatable(0) let sink = MockUpdateSink() test.updates.add(sink) sink.expecting(["begin", "0 -> 1", "end"]) { test.apply(TestChange([0, 1])) } test.updates.remove(sink) } #if false // TODO Compiler crash in Xcode 8.3.2 func test_Connector_connectObservableToUpdateSink() { let observable = TestObservable(0) let connector = Connector() var received: [Update] = [] connector.connect(observable) { update in received.append(update) } observable.value = 1 XCTAssertEqual(received.map { "\($0)" }, ["beginTransaction", "change(0 -> 1)", "endTransaction"]) received = [] connector.disconnect() observable.value = 2 XCTAssertEqual(received.map { "\($0)" }, []) } #endif #if false // TODO Compiler crash in Xcode 8.3.2 func test_Connector_connectObservableToChangeSink() { let observable = TestObservable(0) let connector = Connector() var received: [TestChange] = [] connector.connect(observable) { change in received.append(change) } observable.value = 1 XCTAssertEqual(received.map { "\($0)" }, ["0 -> 1"]) received = [] connector.disconnect() observable.value = 2 XCTAssertEqual(received.map { "\($0)" }, []) } #endif } ================================================ FILE: Tests/GlueKitTests/ObservableValueTests.swift ================================================ // // ObservableValueTests.swift // GlueKit // // Created by Károly Lőrentey on 2015-12-08. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest @testable import GlueKit class ObservableValueTests: XCTestCase { func test_anyObservable_fromObservableValue() { let test = TestObservableValue(0) let any = test.anyObservableValue XCTAssertEqual(any.value, 0) let updateSink = MockValueUpdateSink(any.updates) let changeSink = TransformedMockSink, String>({ "\($0.old) -> \($0.new)" }) changeSink.subscribe(to: any.changes) let valuesSink = MockSink() valuesSink.expecting(0) { valuesSink.subscribe(to: any.values) } let futureValuesSink = MockSink(any.futureValues) updateSink.expecting("begin") { test.beginTransaction() } updateSink.expecting("0 -> 1") { test.value = 1 } updateSink.expecting("end") { changeSink.expecting("0 -> 1") { valuesSink.expecting(1) { futureValuesSink.expecting(1) { test.endTransaction() } } } } } func test_anyObservable_fromClosures() { var value = 0 let signal = Signal>() let test = AnyObservableValue(getter: { value }, updates: signal.anySource) let any = test.anyObservableValue XCTAssertEqual(any.value, 0) let updateSink = MockValueUpdateSink() updateSink.subscribe(to: any.updates) let changeSink = TransformedMockSink, String>({ "\($0.old) -> \($0.new)" }) changeSink.subscribe(to: any.changes) let valuesSink = MockSink() valuesSink.expecting(0) { valuesSink.subscribe(to: any.values) } let futureValuesSink = MockSink(any.futureValues) updateSink.expecting("begin") { signal.send(.beginTransaction) } updateSink.expecting("0 -> 1") { value = 1 signal.send(.change(ValueChange(from: 0, to: 1))) } updateSink.expecting("end") { changeSink.expecting("0 -> 1") { valuesSink.expecting(1) { futureValuesSink.expecting(1) { signal.send(.endTransaction) } } } } } func testObservableValueType_values_SendsInitialValue() { let test = TestObservableValue(0) var res = [Int]() let connection = test.values.subscribe { res.append($0) } XCTAssertEqual(res, [0]) test.value = 1 test.value = 2 connection.disconnect() test.value = 3 XCTAssertEqual(res, [0, 1, 2]) } func testObservableValueType_values_SupportsNestedSendsBySerializingThem() { let test = TestObservableValue(0) var s = "" let c1 = test.values.subscribe { i in s += " (\(i)" if i > 0 { test.value = i - 1 } s += ")" } let c2 = test.values.subscribe { i in s += " (\(i)" if i > 0 { test.value = i - 1 } s += ")" } XCTAssertEqual(s, " (0) (0)") s = "" test.value = 2 XCTAssertEqual(s, " (2) (2) (1) (1) (1) (1) (0) (0) (0) (0) (0) (0) (0) (0)") c1.disconnect() c2.disconnect() } func testObservableValueType_constant() { let constant = AnyObservableValue.constant(1) XCTAssertEqual(constant.value, 1) let sink = MockValueUpdateSink() constant.add(sink) let sink2 = MockValueUpdateSink() let removed = constant.remove(sink2) XCTAssert(removed === sink2) } } ================================================ FILE: Tests/GlueKitTests/RefListTests.swift ================================================ // // RefListTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-08. // Copyright © 2015–2017 Károly Lőrentey. // import Foundation import XCTest @testable import GlueKit private final class Fixture: RefListElement { var value: Int var refListLink = RefListLink() init(_ value: Int) { self.value = value } } class RefListTests: XCTestCase { private func verify(_ list: RefList, file: StaticString = #file, line: UInt = #line) { for i in 0 ..< list.count { let element = list[i] XCTAssertEqual(list.index(of: element), i, file: file, line: line) } } func test_emptyInitializer() { let list = RefList() XCTAssertTrue(list.isEmpty) XCTAssertEqual(Array(list).map { $0.value }, []) verify(list) } func test_initializerFromSequence() { let list = RefList((0 ..< 1000).map { Fixture($0) }) XCTAssertFalse(list.isEmpty) XCTAssertEqual(Array(list).map { $0.value }, Array(0 ..< 1000)) verify(list) } func test_basicOperations() { let list = RefList(order: 5) // Append even elements. for i in 0 ..< 50 { list.append(Fixture(2 * i)) verify(list) } XCTAssertEqual(list.map { $0.value }, (0 ..< 50).map { 2 * $0 }) // Insert odd elements. for i in 0 ..< 50 { list.insert(Fixture(2 * i + 1), at: 2 * i + 1) verify(list) } XCTAssertEqual(list.map { $0.value }, Array(0 ..< 100)) // Look up elements. for i in 0 ..< 100 { let element = list[i] XCTAssertEqual(element.value, i) } // Remove elements from the start. for i in 0 ..< 50 { XCTAssertEqual(list.remove(at: 0).value, i) verify(list) } // Remove elements from the end. for i in (50 ..< 100).reversed() { XCTAssertEqual(list.remove(at: list.count - 1).value, i) verify(list) } } func test_remove() { let c = 30 for removedIndex in 0 ..< c { let list = RefList(order: 5) let elements = (0 ..< c).map { Fixture($0) } list.append(contentsOf: elements) XCTAssertEqual(list.remove(at: removedIndex).value, removedIndex) verify(list) var expected = elements expected.remove(at: removedIndex) XCTAssertTrue(list.elementsEqual(expected, by: ===)) } } func test_insertCollection() { let c = 30 for insertionIndex in 0 ..< c { let list = RefList(order: 5) let origElements = (0 ..< c).map { Fixture($0) } list.append(contentsOf: origElements) let insertedElements = (0 ..< 10).map { Fixture(100 + $0) } list.insert(contentsOf: insertedElements, at: insertionIndex) verify(list) var expected = origElements expected.insert(contentsOf: insertedElements, at: insertionIndex) XCTAssertTrue(list.elementsEqual(expected, by: ===)) } } func test_removeSubrange() { let c = 30 for start in 0 ..< c { for end in start ..< c { let list = RefList(order: 5) let elements = (0 ..< c).map { Fixture($0) } list.append(contentsOf: elements) list.removeSubrange(start ..< end) verify(list) var expected = elements expected.removeSubrange(start ..< end) XCTAssertTrue(list.elementsEqual(expected, by: ===)) } } } func test_replaceSubrange() { let c = 30 for start in 0 ..< c { for end in start ..< c { for newRange in [0 ..< 0, 0 ..< 1, 0 ..< 10] { let list = RefList(order: 5) let elements = (0 ..< c).map { Fixture($0) } list.append(contentsOf: elements) let replacement = newRange.map { Fixture(100 + $0) } list.replaceSubrange(start ..< end, with: replacement) verify(list) var expected = elements expected.replaceSubrange(start ..< end, with: replacement) XCTAssertTrue(list.elementsEqual(expected, by: ===)) } } } } func test_subscript_setter() { let list = RefList(order: 5) list.append(contentsOf: (0 ..< 30).map { Fixture($0) }) for i in 0 ..< 30 { list[i] = Fixture(2 * i) verify(list) } XCTAssertEqual(list.map { $0.value }, (0 ..< 30).map { 2 * $0 }) } func test_rangeSubscript() { let list = RefList(order: 5) list.append(contentsOf: (0 ..< 30).map { Fixture($0) }) for start in 0 ..< 30 { for end in start ..< 30 { let slice = list[start ..< end] XCTAssertEqual(slice.map { $0.value }, Array(start ..< end)) } } } func test_leaks() { weak var list: RefList? = nil weak var test: Fixture? = nil do { let l = RefList(order: 5) l.append(contentsOf: (0 ..< 30).map { Fixture($0) }) list = l test = l[10] } XCTAssertNil(list) XCTAssertNil(test) } func test_forEach() { let list = RefList(order: 5) list.append(contentsOf: (0 ..< 100).map { Fixture($0) }) var i = 0 list.forEach { e in XCTAssertEqual(e.value, i) i += 1 } XCTAssertEqual(i, list.count) for start in 0 ..< list.count { for end in start ..< list.count { var i = start list.forEach(in: start ..< end) { e in XCTAssertEqual(e.value, i) i += 1 } XCTAssertEqual(i, end) } } } } ================================================ FILE: Tests/GlueKitTests/SetBufferingTests.swift ================================================ // // SetBufferingTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-11-02. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest @testable import GlueKit class SetBufferingTests: XCTestCase { func test_connectsImmediately() { let observable = TestObservableSet([1, 2, 3]) do { let buffered = observable.buffered() XCTAssertTrue(observable.isConnected) withExtendedLifetime(buffered) {} } XCTAssertFalse(observable.isConnected) } func test_properties() { let observable = TestObservableSet([1, 2, 3]) let buffered = observable.buffered() for b in [buffered, buffered.buffered()] { XCTAssertEqual(b.isBuffered, true) XCTAssertEqual(b.count, 3) XCTAssertEqual(b.value, [1, 2, 3]) XCTAssertEqual(b.contains(0), false) XCTAssertEqual(b.contains(1), true) XCTAssertEqual(b.isSubset(of: [1, 2, 3, 4]), true) XCTAssertEqual(b.isSubset(of: [2, 3, 4, 5]), false) XCTAssertEqual(b.isSuperset(of: [1, 2]), true) XCTAssertEqual(b.isSuperset(of: [3, 4]), false) } observable.apply(SetChange(removed: [3], inserted: [0])) XCTAssertEqual(buffered.value, [0, 1, 2]) } func test_updates() { let observable = TestObservableSet([1, 2, 3]) let buffered = observable.buffered() let sink = MockSetObserver(buffered) sink.expecting(["begin", "[3]/[]", "end"]) { observable.apply(SetChange(removed: [3])) } XCTAssertEqual(buffered.value, [1, 2]) sink.expecting("begin") { observable.beginTransaction() } sink.expectingNothing { observable.apply(SetChange(removed: [2], inserted: [4])) } XCTAssertEqual(buffered.value, [1, 2]) sink.expectingNothing { observable.apply(SetChange(inserted: [9])) } XCTAssertEqual(buffered.value, [1, 2]) sink.expecting(["[2]/[4, 9]", "end"]) { observable.endTransaction() } XCTAssertEqual(buffered.value, [1, 4, 9]) sink.disconnect() } } ================================================ FILE: Tests/GlueKitTests/SetFilteringTests.swift ================================================ // // SetFilteringTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-06. // Copyright © 2015–2017 Károly Lőrentey. // import Foundation import XCTest import GlueKit class SetFilteringTests: XCTestCase { func test_filter_simplePredicate() { let set = SetVariable(0 ..< 10) let even = set.filter { $0 & 1 == 0 } XCTAssertFalse(even.isBuffered) XCTAssertEqual(even.count, 5) XCTAssertEqual(even.value, [0, 2, 4, 6, 8]) XCTAssertTrue(even.contains(0)) XCTAssertFalse(even.contains(1)) XCTAssertTrue(even.isSubset(of: Set(0 ..< 10))) XCTAssertTrue(even.isSubset(of: Set(-1 ..< 11))) XCTAssertFalse(even.isSubset(of: Set(1 ..< 20))) XCTAssertTrue(even.isSuperset(of: [])) XCTAssertTrue(even.isSuperset(of: [2, 4, 6])) XCTAssertTrue(even.isSuperset(of: [0, 2, 4, 6, 8])) XCTAssertFalse(even.isSuperset(of: [2, 5, 6])) let mock = MockSetObserver(even) // Repeat basic tests with an active connection. XCTAssertFalse(even.isBuffered) XCTAssertEqual(even.count, 5) XCTAssertEqual(even.value, [0, 2, 4, 6, 8]) XCTAssertTrue(even.contains(0)) XCTAssertFalse(even.contains(1)) XCTAssertTrue(even.isSubset(of: Set(0 ..< 10))) XCTAssertTrue(even.isSubset(of: Set(-1 ..< 11))) XCTAssertFalse(even.isSubset(of: Set(1 ..< 20))) XCTAssertTrue(even.isSuperset(of: [])) XCTAssertTrue(even.isSuperset(of: [2, 4, 6])) XCTAssertTrue(even.isSuperset(of: [0, 2, 4, 6, 8])) XCTAssertFalse(even.isSuperset(of: [2, 5, 6])) // Now try some modifications mock.expecting(["begin", "[]/[10]", "end"]) { set.insert(10) } XCTAssertEqual(even.value, [0, 2, 4, 6, 8, 10]) mock.expecting(["begin", "end"]) { set.insert(11) } XCTAssertEqual(even.value, [0, 2, 4, 6, 8, 10]) mock.expecting(["begin", "end"]) { set.remove(5) } XCTAssertEqual(even.value, [0, 2, 4, 6, 8, 10]) mock.expecting(["begin", "[6]/[]", "end"]) { set.remove(6) } XCTAssertEqual(even.value, [0, 2, 4, 8, 10]) } func test_filter_observableBool() { var f = (0 ..< 15).map { Foo($0) } let set = SetVariable(f[0 ..< 10]) let even = set.filter { $0.isEven } XCTAssertFalse(even.isBuffered) XCTAssertEqual(even.count, 5) XCTAssertEqual(even.value, [f[0], f[2], f[4], f[6], f[8]]) XCTAssertTrue(even.contains(f[0])) XCTAssertFalse(even.contains(f[1])) XCTAssertTrue(even.isSubset(of: Set(f))) XCTAssertTrue(even.isSubset(of: Set(f + [Foo(10), Foo(-1)]))) XCTAssertFalse(even.isSubset(of: [f[0], f[2], f[5], f[6], f[8]])) XCTAssertTrue(even.isSuperset(of: [])) XCTAssertTrue(even.isSuperset(of: [f[2], f[4], f[6]])) XCTAssertTrue(even.isSuperset(of: [f[0], f[2], f[4], f[6], f[8]])) XCTAssertFalse(even.isSuperset(of: [f[2], f[5], f[6]])) let mock = MockSetObserver(even) // Repeat basic tests with an active connection. XCTAssertFalse(even.isBuffered) XCTAssertEqual(even.count, 5) XCTAssertEqual(even.value, [f[0], f[2], f[4], f[6], f[8]]) XCTAssertTrue(even.contains(f[0])) XCTAssertFalse(even.contains(f[1])) XCTAssertTrue(even.isSubset(of: Set(f))) XCTAssertTrue(even.isSubset(of: Set(f + [Foo(10), Foo(-1)]))) XCTAssertFalse(even.isSubset(of: [f[0], f[2], f[5], f[6], f[8]])) XCTAssertTrue(even.isSuperset(of: [])) XCTAssertTrue(even.isSuperset(of: [f[2], f[4], f[6]])) XCTAssertTrue(even.isSuperset(of: [f[0], f[2], f[4], f[6], f[8]])) XCTAssertFalse(even.isSuperset(of: [f[2], f[5], f[6]])) // Now try some modifications mock.expecting(["begin", "[]/[10]", "end"]) { set.insert(f[10]) } XCTAssertEqual(even.value, [f[0], f[2], f[4], f[6], f[8], f[10]]) mock.expecting(["begin", "end"]) { set.insert(f[11]) } XCTAssertEqual(even.value, [f[0], f[2], f[4], f[6], f[8], f[10]]) mock.expecting(["begin", "end"]) { set.remove(f[3]) } XCTAssertEqual(even.value, [f[0], f[2], f[4], f[6], f[8], f[10]]) mock.expecting(["begin", "[4]/[]", "end"]) { set.remove(f[4]) } XCTAssertEqual(even.value, [f[0], f[2], f[6], f[8], f[10]]) mock.expecting(["begin", "[]/[11]", "end"]) { f[11].number.value = 10 } XCTAssertEqual(even.value, [f[0], f[2], f[6], f[8], f[10], f[11]]) mock.expecting(["begin", "[8]/[]", "end"]) { f[8].number.value = 9 } XCTAssertEqual(even.value, [f[0], f[2], f[6], f[10], f[11]]) mock.expecting(["begin", "end"]) { f[8].number.value = 7 } XCTAssertEqual(even.value, [f[0], f[2], f[6], f[10], f[11]]) mock.expecting(["begin", "end"]) { f[6].number.value = 8 } XCTAssertEqual(even.value, [f[0], f[2], f[6], f[10], f[11]]) } func test_filter_observablePredicate() { let predicate = Variable<(Int) -> Bool> { $0 & 1 == 0 } let set = SetVariable(0 ..< 10) let filtered = set.filter(predicate) XCTAssertEqual(filtered.value, [0, 2, 4, 6, 8]) let mock = MockSetObserver(filtered) mock.expecting(["begin", "[]/[10]", "end"]) { set.insert(10) } mock.expecting(["begin", "[6, 8, 10]/[1, 3, 5]", "end"]) { predicate.value = { $0 <= 5 } } mock.expecting(["begin", "[]/[-1]", "end"]) { set.insert(-1) } mock.expecting(["begin", "[0, 2, 4]/[7, 9]", "end"]) { predicate.value = { $0 & 1 == 1 } } } func test_filter_observableOptionalPredicate() { let predicate = Variable Bool>> { $0 & 1 == 0 } let set = SetVariable(0 ..< 10) let filtered = set.filter(predicate) XCTAssertEqual(filtered.value, [0, 2, 4, 6, 8]) let mock = MockSetObserver(filtered) mock.expecting(["begin", "[]/[10]", "end"]) { set.insert(10) } mock.expecting(["begin", "[6, 8, 10]/[1, 3, 5]", "end"]) { predicate.value = { $0 <= 5 } } mock.expecting(["begin", "[]/[-1]", "end"]) { set.insert(-1) } mock.expecting(["begin", "[0, 2, 4]/[7, 9]", "end"]) { predicate.value = { $0 & 1 == 1 } } mock.expecting(["begin", "[]/[0, 2, 4, 6, 8, 10]", "end"]) { predicate.value = nil } } } private final class Foo: Hashable, Comparable, ExpressibleByIntegerLiteral, CustomStringConvertible { let id: Int let number: IntVariable var isEven: AnyObservableValue { return number.map { $0 & 1 == 0 } } init(_ number: Int) { self.id = number self.number = .init(number) } convenience init(integerLiteral value: Int) { self.init(value) } var hashValue: Int { return ObjectIdentifier(self).hashValue } var description: String { return "\(id)" } static func == (a: Foo, b: Foo) -> Bool { return a === b } static func < (a: Foo, b: Foo) -> Bool { return a.number.value < b.number.value } } ================================================ FILE: Tests/GlueKitTests/SetFoldingTests.swift ================================================ // // SetFoldingTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-09. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest import GlueKit class SetFoldingTests: XCTestCase { func testSum() { let set = SetVariable([1, 2, 3]) let sum = set.sum() XCTAssertEqual(sum.value, 6) set.insert(4) XCTAssertEqual(sum.value, 10) set.formUnion([5, 6]) XCTAssertEqual(sum.value, 21) set.remove(1) XCTAssertEqual(sum.value, 20) set.subtract([2, 4]) XCTAssertEqual(sum.value, 14) set.removeAll() XCTAssertEqual(sum.value, 0) } } ================================================ FILE: Tests/GlueKitTests/SetMappingTests.swift ================================================ // // SetMappingTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-05. // Copyright © 2015–2017 Károly Lőrentey. // import Foundation import XCTest import GlueKit private class Book: Hashable { let title: StringVariable let authors: SetVariable let chapters: ArrayVariable init(_ title: String, authors: Set = [], chapters: [String] = []) { self.title = .init(title) self.authors = .init(authors) self.chapters = .init(chapters) } var hashValue: Int { return ObjectIdentifier(self).hashValue } static func ==(a: Book, b: Book) -> Bool { return a === b } } class SetMappingTests: XCTestCase { func test_injectiveMap() { let set = SetVariable([0, 2, 3]) let mappedSet = set.injectiveMap { "\($0)" } XCTAssertTrue(mappedSet.isBuffered) XCTAssertEqual(mappedSet.count, 3) XCTAssertEqual(mappedSet.value, Set(["0", "2", "3"])) XCTAssertEqual(mappedSet.contains("0"), true) XCTAssertEqual(mappedSet.contains("1"), false) XCTAssertEqual(mappedSet.isSubset(of: []), false) XCTAssertEqual(mappedSet.isSubset(of: ["3", "4", "5"]), false) XCTAssertEqual(mappedSet.isSubset(of: ["0", "2", "3"]), true) XCTAssertEqual(mappedSet.isSubset(of: ["0", "1", "2", "3"]), true) XCTAssertEqual(mappedSet.isSuperset(of: []), true) XCTAssertEqual(mappedSet.isSuperset(of: ["3"]), true) XCTAssertEqual(mappedSet.isSuperset(of: ["0", "2", "3"]), true) XCTAssertEqual(mappedSet.isSuperset(of: ["0", "1", "2", "3"]), false) XCTAssertEqual(mappedSet.isSuperset(of: ["1"]), false) let mock = MockSetObserver(mappedSet) mock.expecting(["begin", "[]/[1]", "end"]) { set.insert(1) } XCTAssertEqual(mappedSet.value, Set(["0", "1", "2", "3"])) mock.expecting(["begin", "[1, 2]/[]", "end"]) { set.subtract(Set([1, 2])) } XCTAssertEqual(mappedSet.value, Set(["0", "3"])) } func test_map_injectiveValue() { let set = SetVariable([0, 2, 3]) let mappedSet = set.map { "\($0)" } XCTAssertFalse(mappedSet.isBuffered) XCTAssertEqual(mappedSet.count, 3) XCTAssertEqual(mappedSet.value, Set(["0", "2", "3"])) XCTAssertEqual(mappedSet.contains("0"), true) XCTAssertEqual(mappedSet.contains("1"), false) XCTAssertEqual(mappedSet.isSubset(of: []), false) XCTAssertEqual(mappedSet.isSubset(of: ["3", "4", "5"]), false) XCTAssertEqual(mappedSet.isSubset(of: ["0", "2", "3"]), true) XCTAssertEqual(mappedSet.isSubset(of: ["0", "1", "2", "3"]), true) XCTAssertEqual(mappedSet.isSuperset(of: []), true) XCTAssertEqual(mappedSet.isSuperset(of: ["3"]), true) XCTAssertEqual(mappedSet.isSuperset(of: ["0", "2", "3"]), true) XCTAssertEqual(mappedSet.isSuperset(of: ["0", "1", "2", "3"]), false) XCTAssertEqual(mappedSet.isSuperset(of: ["1"]), false) let mock = MockSetObserver(mappedSet) mock.expecting(["begin", "[]/[1]", "end"]) { set.insert(1) } XCTAssertEqual(mappedSet.value, Set(["0", "1", "2", "3"])) mock.expecting(["begin", "[1, 2]/[]", "end"]) { set.subtract(Set([1, 2])) } XCTAssertEqual(mappedSet.value, Set(["0", "3"])) } func test_map_noninjectiveValue() { let set = SetVariable([0, 2, 3, 4, 8, 9]) let mappedSet = set.map { $0 / 2 } XCTAssertEqual(mappedSet.value, [0, 1, 2, 4]) let mock = MockSetObserver(mappedSet) mock.expecting(["begin", "end"]) { set.insert(1) } XCTAssertEqual(mappedSet.value, [0, 1, 2, 4]) mock.expecting(["begin", "[2]/[]", "end"]) { set.remove(4) } XCTAssertEqual(mappedSet.value, [0, 1, 4]) mock.expecting(["begin", "end"]) { set.remove(3) } XCTAssertEqual(mappedSet.value, [0, 1, 4]) } func test_map_valueField() { let b1 = Book("foo") let b2 = Book("bar") let b3 = Book("baz") let books: SetVariable = [b1, b2, b3] let titles = books.map { $0.title } XCTAssertEqual(titles.value, ["foo", "bar", "baz"]) let mock = MockSetObserver(titles) let b4 = Book("fred") mock.expecting(["begin", "[]/[fred]", "end"]) { books.insert(b4) } XCTAssertEqual(titles.value, ["foo", "bar", "baz", "fred"]) mock.expecting(["begin", "[bar]/[]", "end"]) { books.remove(b2) } XCTAssertEqual(titles.value, ["foo", "baz", "fred"]) mock.expecting(["begin", "[baz]/[bazaar]", "end"]) { b3.title.value = "bazaar" } XCTAssertEqual(titles.value, ["foo", "bazaar", "fred"]) mock.expecting(["begin", "end"]) { b3.title.value = "bazaar" } XCTAssertEqual(titles.value, ["foo", "bazaar", "fred"]) mock.expectingNothing { b2.title.value = "xyzzy" // b2 isn't in books } XCTAssertEqual(titles.value, ["foo", "bazaar", "fred"]) mock.expecting(["begin", "[foo]/[xyzzy]", "end"]) { b1.title.value = "xyzzy" } XCTAssertEqual(titles.value, ["xyzzy", "bazaar", "fred"]) mock.expecting(["begin", "end"]) { books.insert(b2) } mock.expecting(["begin", "end"]) { books.remove(b1) } XCTAssertEqual(titles.value, ["xyzzy", "bazaar", "fred"]) mock.expecting(["begin", "[xyzzy]/[]", "end"]) { books.remove(b2) } XCTAssertEqual(titles.value, ["bazaar", "fred"]) mock.expecting(["begin", "[fred]/[fuzzy]", "end"]) { b4.title.value = "fuzzy" } XCTAssertEqual(titles.value, ["bazaar", "fuzzy"]) mock.expecting(["begin", "[bazaar]/[]", "end"]) { b3.title.value = "fuzzy" } XCTAssertEqual(titles.value, ["fuzzy"]) } func test_flatMap_setField() { let b1 = Book("1", authors: ["a", "b", "c"]) let b2 = Book("2", authors: ["a"]) let b3 = Book("3", authors: ["b", "d"]) let books: SetVariable = [b1, b2, b3] let authors = books.flatMap { $0.authors } XCTAssertEqual(authors.value, ["a", "b", "c", "d"]) let mock = MockSetObserver(authors) let b4 = Book("4", authors: ["b", "c", "e"]) mock.expecting(["begin", "[]/[e]", "end"]) { books.insert(b4) } XCTAssertEqual(authors.value, ["a", "b", "c", "d", "e"]) mock.expecting(["begin", "end"]) { books.remove(b1) } XCTAssertEqual(authors.value, ["a", "b", "c", "d", "e"]) mock.expecting(["begin", "[c]/[]", "end"]) { b4.authors.remove("c") } XCTAssertEqual(authors.value, ["a", "b", "d", "e"]) mock.expecting(["begin", "[]/[c]", "end"]) { b4.authors.insert("c") } XCTAssertEqual(authors.value, ["a", "b", "c", "d", "e"]) mock.expecting(["begin", "[a]/[f]", "end"]) { b2.authors.value = ["f"] } XCTAssertEqual(authors.value, ["b", "c", "d", "e", "f"]) mock.expecting(["begin", "[d]/[]", "end"]) { books.remove(b3) } XCTAssertEqual(authors.value, ["b", "c", "e", "f"]) mock.expecting(["begin", "[f]/[]", "end"]) { books.remove(b2) } XCTAssertEqual(authors.value, ["b", "c", "e"]) } func test_flatMap_arrayField() { let b1 = Book("1", chapters: ["a", "b", "c"]) let b2 = Book("2", chapters: ["a"]) let b3 = Book("3", chapters: ["b", "d"]) let books: SetVariable = [b1, b2, b3] // It isn't very useful to make a set of chapters from several books, but let's do that anyway. let chapters = books.flatMap { $0.chapters } XCTAssertEqual(chapters.value, ["a", "b", "c", "d"]) let mock = MockSetObserver(chapters) let b4 = Book("4", chapters: ["b", "c", "e"]) mock.expecting(["begin", "[]/[e]", "end"]) { books.insert(b4) } XCTAssertEqual(chapters.value, ["a", "b", "c", "d", "e"]) mock.expecting(["begin", "end"]) { books.remove(b1) } XCTAssertEqual(chapters.value, ["a", "b", "c", "d", "e"]) mock.expecting(["begin", "[c]/[]", "end"]) { _ = b4.chapters.remove(at: 1) } // b4.chapters was bce XCTAssertEqual(chapters.value, ["a", "b", "d", "e"]) mock.expecting(["begin", "[]/[c]", "end"]) { b4.chapters.insert("c", at: 1) } // b4.chapters was be XCTAssertEqual(chapters.value, ["a", "b", "c", "d", "e"]) mock.expecting(["begin", "end"]) { b4.chapters.value = ["e", "c", "b"] } // Reordering chapters has no effect on result set XCTAssertEqual(chapters.value, ["a", "b", "c", "d", "e"]) mock.expecting(["begin", "[a]/[f]", "end"]) { b2.chapters.value = ["f"] } XCTAssertEqual(chapters.value, ["b", "c", "d", "e", "f"]) mock.expecting(["begin", "[d]/[]", "end"]) { books.remove(b3) } XCTAssertEqual(chapters.value, ["b", "c", "e", "f"]) mock.expecting(["begin", "[f]/[]", "end"]) { books.remove(b2) } XCTAssertEqual(chapters.value, ["b", "c", "e"]) } func test_flatMap_sequence() { let b1 = Book("1", authors: ["a", "b", "c"]) let b2 = Book("2", authors: ["a"]) let b3 = Book("3", authors: ["b", "d"]) let books: SetVariable = [b1, b2, b3] // In this variant, we extract the value of the authors field, so that we have a simple flatMap where the // transform closure just returns a sequence. This means that the resulting observable does not track changes // to the values of individual fields, which is normally a bad idea -- but for this test, it spares us from // having to add a sequence-typed property to Book. let authors = books.flatMap { $0.authors.value } XCTAssertEqual(authors.value, ["a", "b", "c", "d"]) let mock = MockSetObserver(authors) let b4 = Book("4", authors: ["b", "c", "e"]) mock.expecting(["begin", "[]/[e]", "end"]) { books.insert(b4) } XCTAssertEqual(authors.value, ["a", "b", "c", "d", "e"]) mock.expecting(["begin", "end"]) { books.remove(b1) } XCTAssertEqual(authors.value, ["a", "b", "c", "d", "e"]) mock.expecting(["begin", "[d]/[]", "end"]) { books.remove(b3) } XCTAssertEqual(authors.value, ["a", "b", "c", "e"]) mock.expecting(["begin", "[a]/[]", "end"]) { books.remove(b2) } XCTAssertEqual(authors.value, ["b", "c", "e"]) } } ================================================ FILE: Tests/GlueKitTests/SetReferenceTests.swift ================================================ // // SetReferenceTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-11-02. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest import GlueKit class SetReferenceTests: XCTestCase { func testReference() { let a: SetVariable = [1, 2, 3] let b: SetVariable = [10, 20] let c: SetVariable = [7] let ref = Variable>(a) XCTAssertEqual(ref.value.value, Set([1, 2, 3])) a.insert(4) XCTAssertEqual(ref.value.value, Set([1, 2, 3, 4])) let unpacked = ref.unpacked() XCTAssertEqual(unpacked.isBuffered, false) XCTAssertEqual(unpacked.count, 4) XCTAssertEqual(unpacked.value, Set([1, 2, 3, 4])) XCTAssertEqual(unpacked.contains(0), false) XCTAssertEqual(unpacked.contains(1), true) XCTAssertEqual(unpacked.isSubset(of: [1, 2, 3, 4, 5]), true) XCTAssertEqual(unpacked.isSubset(of: [2, 3, 4, 5, 6]), false) XCTAssertEqual(unpacked.isSuperset(of: [1, 2, 3]), true) XCTAssertEqual(unpacked.isSuperset(of: [3, 4, 5]), false) a.remove(2) XCTAssertEqual(unpacked.value, [1, 3, 4]) let sink = MockSetObserver(unpacked) sink.expecting(["begin", "[1]/[]", "end"]) { a.remove(1) } sink.expecting(["begin", "[3, 4]/[10, 20]", "end"]) { ref.value = b } sink.expecting("begin") { b.apply(.beginTransaction) } sink.expectingNothing { ref.apply(.beginTransaction) } sink.expecting("[]/[15]") { b.insert(15) } sink.expecting("[10, 15, 20]/[7]") { ref.value = c } sink.expecting("[]/[8]") { c.insert(8) } sink.expectingNothing { b.apply(.endTransaction) } sink.expecting("end") { ref.apply(.endTransaction) } sink.disconnect() } } ================================================ FILE: Tests/GlueKitTests/SetSortingTests.swift ================================================ // // SetSortingTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-05. // Copyright © 2015–2017 Károly Lőrentey. // import Foundation import XCTest import GlueKit extension ArrayModification { fileprivate func dumped() -> String { switch self { case .insert(let e, at: let i): return "insert(\(e), at: \(i))" case .remove(let e, at: let i): return "remove(\(e), at: \(i))" case .replace(let old, at: let i, with: let new): return "replace(\(old), at: \(i), with: \(new))" case .replaceSlice(let old, at: let i, with: let new): let o = old.map { "\($0)" }.joined(separator: ", ") let n = new.map { "\($0)" }.joined(separator: ", ") return "replaceSlice([\(o)], at: \(i), with: [\(n)])" } } } extension ArrayChange { fileprivate func dumped() -> String { return modifications.lazy.map { $0.dumped() }.joined(separator: ", ") } } private class Book: Hashable, CustomStringConvertible { let title: StringVariable init(_ title: String) { self.title = .init(title) } var hashValue: Int { return ObjectIdentifier(self).hashValue } var description: String { return "Book(\(title.value))" } static func ==(a: Book, b: Book) -> Bool { return a === b } } class SetSortingTests: XCTestCase { func test_sortedSetUsingIdentityTransform() { let set = SetVariable([0, 2, 3, 4, 8, 9]) let sortedSet = set.sorted() XCTAssertEqual(sortedSet.value, [0, 2, 3, 4, 8, 9]) XCTAssertEqual(sortedSet.isBuffered, false) XCTAssertEqual(sortedSet.count, 6) XCTAssertEqual(sortedSet[0], 0) XCTAssertEqual(sortedSet[1], 2) XCTAssertEqual(sortedSet[2 ..< 4], ArraySlice([3, 4])) var actualChanges: [String] = [] var expectedChanges: [String] = [] let connection = sortedSet.changes.subscribe { change in actualChanges.append(change.dumped()) } set.insert(1) XCTAssertEqual(sortedSet.value, [0, 1, 2, 3, 4, 8, 9]) expectedChanges.append("insert(1, at: 1)") XCTAssertEqual(actualChanges, expectedChanges) set.remove(3) XCTAssertEqual(sortedSet.value, [0, 1, 2, 4, 8, 9]) expectedChanges.append("remove(3, at: 3)") XCTAssertEqual(actualChanges, expectedChanges) set.formUnion([3, 5, 6]) XCTAssertEqual(sortedSet.value, [0, 1, 2, 3, 4, 5, 6, 8, 9]) expectedChanges.append("insert(3, at: 3), replaceSlice([], at: 5, with: [5, 6])") XCTAssertEqual(actualChanges, expectedChanges) connection.disconnect() } func test_sortedSetInReverse() { let set = SetVariable([0, 2, 3, 4, 8, 9]) let sortedSet = set.sorted(by: >) XCTAssertEqual(sortedSet.value, [9, 8, 4, 3, 2, 0]) var actualChanges: [String] = [] var expectedChanges: [String] = [] let connection = sortedSet.changes.subscribe { change in actualChanges.append(change.dumped()) } set.insert(1) XCTAssertEqual(sortedSet.value, [9, 8, 4, 3, 2, 1, 0]) expectedChanges.append("insert(1, at: 5)") XCTAssertEqual(actualChanges, expectedChanges) set.remove(3) XCTAssertEqual(sortedSet.value, [9, 8, 4, 2, 1, 0]) expectedChanges.append("remove(3, at: 3)") XCTAssertEqual(actualChanges, expectedChanges) set.formUnion([3, 5, 6]) XCTAssertEqual(sortedSet.value, [9, 8, 6, 5, 4, 3, 2, 1, 0]) expectedChanges.append("replaceSlice([], at: 2, with: [6, 5]), insert(3, at: 5)") XCTAssertEqual(actualChanges, expectedChanges) connection.disconnect() } func test_sortedSetUsingNoninjectiveTransform() { let set = SetVariable([0, 2, 3, 4, 8, 9]) let sortedSet = set.sortedMap { $0 / 2 } XCTAssertEqual(sortedSet.value, [0, 1, 2, 4]) var actualChanges: [String] = [] var expectedChanges: [String] = [] let connection = sortedSet.changes.subscribe { change in actualChanges.append(change.dumped()) } set.remove(2) XCTAssertEqual(sortedSet.value, [0, 1, 2, 4]) XCTAssertEqual(actualChanges, expectedChanges) // No change expected (2 and 3 both mapped to 1). set.remove(3) XCTAssertEqual(sortedSet.value, [0, 2, 4]) expectedChanges.append("remove(1, at: 1)") XCTAssertEqual(actualChanges, expectedChanges) set.insert(5) XCTAssertEqual(sortedSet.value, [0, 2, 4]) XCTAssertEqual(actualChanges, expectedChanges) // No change expected (4 already mapped to 2). connection.disconnect() } func test_sortedSetUsingObservableTransform() { let b1 = Book("foo") let b2 = Book("bar") let b3 = Book("baz") let b4 = Book("fred") let books = SetVariable([b1, b2, b3]) let sortedTitles = books.sortedMap {$0.title} XCTAssertEqual(sortedTitles.isBuffered, false) XCTAssertEqual(sortedTitles.value, ["bar", "baz", "foo"]) XCTAssertEqual(sortedTitles.count, 3) XCTAssertEqual(sortedTitles[0], "bar") XCTAssertEqual(sortedTitles[1], "baz") XCTAssertEqual(sortedTitles[1 ..< 3], ArraySlice(["baz", "foo"])) var actualChanges: [String] = [] var expectedChanges: [String] = [] let connection = sortedTitles.changes.subscribe { change in actualChanges.append(change.dumped()) } books.insert(b4) XCTAssertEqual(sortedTitles.value, ["bar", "baz", "foo", "fred"]) expectedChanges.append("insert(fred, at: 3)") XCTAssertEqual(actualChanges, expectedChanges) books.subtract([b3, b4]) XCTAssertEqual(sortedTitles.value, ["bar", "foo"]) expectedChanges.append("remove(baz, at: 1), remove(fred, at: 2)") XCTAssertEqual(actualChanges, expectedChanges) b2.title.value = "barney" XCTAssertEqual(sortedTitles.value, ["barney", "foo"]) expectedChanges.append("replace(bar, at: 0, with: barney)") XCTAssertEqual(actualChanges, expectedChanges) b3.title.value = "bazaar" XCTAssertEqual(sortedTitles.value, ["barney", "foo"]) XCTAssertEqual(actualChanges, expectedChanges) // No change expected books.formUnion([b3, b4]) XCTAssertEqual(sortedTitles.value, ["barney", "bazaar", "foo", "fred"]) expectedChanges.append("insert(bazaar, at: 1), insert(fred, at: 3)") XCTAssertEqual(actualChanges, expectedChanges) b3.title.value = "xyzzy" XCTAssertEqual(sortedTitles.value, ["barney", "foo", "fred", "xyzzy"]) expectedChanges.append("remove(bazaar, at: 1), insert(xyzzy, at: 3)") XCTAssertEqual(actualChanges, expectedChanges) b1.title.value = "xyzzy" XCTAssertEqual(sortedTitles.value, ["barney", "fred", "xyzzy"]) expectedChanges.append("remove(foo, at: 1)") XCTAssertEqual(actualChanges, expectedChanges) books.remove(b3) XCTAssertEqual(sortedTitles.value, ["barney", "fred", "xyzzy"]) XCTAssertEqual(actualChanges, expectedChanges) // No change expected (xyzzy had a multiplicity of 2). books.remove(b1) XCTAssertEqual(sortedTitles.value, ["barney", "fred"]) expectedChanges.append("remove(xyzzy, at: 2)") XCTAssertEqual(actualChanges, expectedChanges) b2.title.value = "barney" XCTAssertEqual(sortedTitles.value, ["barney", "fred"]) XCTAssertEqual(actualChanges, expectedChanges) connection.disconnect() } func test_sortedSetUsingObservableComparator() { let set = SetVariable(0..<5) let comparator = Variable<(Int, Int) -> Bool>(<) let sorted = set.sorted(by: comparator) XCTAssertEqual(sorted.value, [0, 1, 2, 3, 4]) comparator.value = (>) XCTAssertEqual(sorted.value, [4, 3, 2, 1, 0]) } } ================================================ FILE: Tests/GlueKitTests/SetVariableTests.swift ================================================ // // SetVariableTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-10. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest import GlueKit private class Foo: Hashable, Comparable, CustomStringConvertible { var i: Int init(_ i: Int) { self.i = i } var hashValue: Int { return i.hashValue } static func ==(a: Foo, b: Foo) -> Bool { return a.i == b.i } static func <(a: Foo, b: Foo) -> Bool { return a.i < b.i } var description: String { return "\(i)" } } class SetVariableTests: XCTestCase { func testInitialization() { let s1 = SetVariable() XCTAssertEqual(s1.value, []) let s2 = SetVariable([1, 2, 3]) XCTAssertEqual(s2.value, [1, 2, 3]) let s3 = SetVariable(Set([1, 2, 3])) XCTAssertEqual(s3.value, [1, 2, 3]) let s4 = SetVariable(1 ... 10) XCTAssertEqual(s4.value, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) let s5 = SetVariable(elements: 1, 2, 3) XCTAssertEqual(s5.value, [1, 2, 3]) } func testUpdate() { let f1 = Foo(1) let f2 = Foo(2) let f3 = Foo(3) let set: SetVariable = [f1, f2, f3] let mock = MockSetObserver(set) let f2p = Foo(2) let a = mock.expecting(["begin", "[2]/[2]", "end"]) { set.update(with: f2p) } XCTAssertTrue(a === f2) let b = mock.expecting(["begin", "[2]/[2]", "end"]) { set.update(with: Foo(2)) } XCTAssertTrue(b === f2p) let c = mock.expecting(["begin", "[]/[4]", "end"]) { set.update(with: Foo(4)) } XCTAssertNil(c) } } ================================================ FILE: Tests/GlueKitTests/SignalTests.swift ================================================ // // SignalTests.swift // GlueKit // // Created by Károly Lőrentey on 2015-11-30. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest @testable import GlueKit class SignalTests: XCTestCase { //MARK: Test add/remove API func test_AddRemove_SinkReceivesValuesWhileAdded() { let signal = Signal() let sink = MockSink() sink.expectingNothing { signal.send(1) } signal.add(sink) sink.expecting([2, 3, 4]) { signal.send(2) signal.send(3) signal.send(4) } signal.remove(sink) sink.expectingNothing { signal.send(5) } } func test_AddRemove_SignalRetainsAddedSinks() { let signal = Signal() weak var weakSink: MockSink? = nil do { let sink = MockSink() weakSink = sink signal.add(sink) } XCTAssertNotNil(weakSink) signal.remove(weakSink!) XCTAssertNil(weakSink) } //MARK: Test subscribe API func test_Connect_DisconnectingTheConnection() { let signal = Signal() var received = [Int]() let c = signal.subscribe { received.append($0) } signal.send(1) c.disconnect() signal.send(2) XCTAssertEqual(received, [1]) noop(c) } func test_Connect_ReleasingConnectionAutomaticallyDisconnects() { let signal = Signal() var values = [Int]() var c: Connection? = signal.subscribe { values.append($0) } signal.send(1) c = nil signal.send(2) XCTAssertEqual(values, [1]) noop(c) } func test_Connect_DuplicateDisconnect() { let signal = Signal() let c = signal.subscribe { i in } // It is OK to call disconnect twice. c.disconnect() c.disconnect() } func test_Connect_MultipleConnections() { let signal = Signal() signal.send(1) var a = [Int]() let c1 = signal.subscribe { i in a.append(i) } signal.send(2) var b = [Int]() let c2 = signal.subscribe { i in b.append(i) } signal.send(3) c1.disconnect() signal.send(4) c2.disconnect() signal.send(5) XCTAssertEqual(a, [2, 3]) XCTAssertEqual(b, [3, 4]) } func test_Connect_ConnectionRetainsTheSignal() { var values = [Int]() weak var weakSignal: Signal? = nil weak var weakConnection: Connection? = nil do { let connection: Connection do { let signal = Signal() weakSignal = signal connection = signal.subscribe { i in values.append(i) } weakConnection = .some(connection) signal.send(1) } XCTAssertNotNil(weakSignal) XCTAssertNotNil(weakConnection) withExtendedLifetime(connection) {} } XCTAssertNil(weakSignal) XCTAssertNil(weakConnection) XCTAssertEqual(values, [1]) } func test_Connect_DisconnectingConnectionReleasesResources() { weak var weakSignal: Signal? = nil weak var weakResource: NSMutableArray? = nil let connection: Connection do { let signal = Signal() weakSignal = signal let resource = NSMutableArray() weakResource = resource connection = signal.subscribe { i in resource.add(i) } signal.send(1) } XCTAssertNotNil(weakSignal) XCTAssertNotNil(weakResource) XCTAssertEqual(weakResource, NSArray(object: 1)) connection.disconnect() XCTAssertNil(weakSignal) XCTAssertNil(weakResource) } func test_Connect_SourceDoesNotRetainConnection() { var values = [Int]() weak var weakConnection: Connection? = nil let signal = Signal() do { let connection = signal.subscribe { values.append($0) } weakConnection = connection signal.send(1) noop(connection) } signal.send(2) XCTAssertNil(weakConnection) XCTAssertEqual(values, [1]) } //MARK: Test sinks adding and removing connections func test_Connect_AddingAConnectionInASink() { let signal = Signal() var v1 = [Int]() var c1: Connection? = nil var v2 = [Int]() var c2: Connection? = nil signal.send(1) c1 = signal.subscribe { i in v1.append(i) if c2 == nil { c2 = signal.subscribe { v2.append($0) } } } XCTAssertNil(c2) signal.send(2) XCTAssertNotNil(c2) signal.send(3) c1?.disconnect() c2?.disconnect() signal.send(4) XCTAssertEqual(v1, [2, 3]) XCTAssertEqual(v2, [3]) } func test_Connect_RemovingConnectionWhileItIsBeingTriggered() { let signal = Signal() signal.send(1) var r = [Int]() var c: Connection? = nil c = signal.subscribe { i in r.append(i) c?.disconnect() } signal.send(2) signal.send(3) signal.send(4) XCTAssertEqual(r, [2]) } func test_Connect_RemovingNextConnection() { let signal = Signal() var r = [Int]() var c1: Connection? = nil var c2: Connection? = nil signal.send(0) // We don't know which connection fires first. // After disconnect() returns, the connection must not fire any more -- even if disconnect is called by a sink. c1 = signal.subscribe { i in r.append(i) c2?.disconnect() c2 = nil } c2 = signal.subscribe { i in r.append(i) c1?.disconnect() c1 = nil } XCTAssertTrue(c1 != nil && c2 != nil) signal.send(1) XCTAssertTrue((c1 == nil) != (c2 == nil)) signal.send(2) signal.send(3) XCTAssertTrue((c1 == nil) != (c2 == nil)) XCTAssertEqual(r, [1, 2, 3]) } func test_Connect_RemovingAndReaddingConnectionsAlternately() { // This is a weaker test of the semantics of subscribe/disconnect nested in sinks. let signal = Signal() var r1 = [Int]() var r2 = [Int]() var c1: Connection? = nil var c2: Connection? = nil var sink1: ((Int) -> Void)! var sink2: ((Int) -> Void)! sink1 = { i in r1.append(i) c1?.disconnect() c2 = signal.subscribe(sink2) } sink2 = { i in r2.append(i) c2?.disconnect() c1 = signal.subscribe(sink1) } c1 = signal.subscribe(sink1) for i in 1...6 { signal.send(i) } XCTAssertEqual(r1, [1, 3, 5]) XCTAssertEqual(r2, [2, 4, 6]) } func test_Connect_SinkDisconnectingThenReconnectingItself() { // This is a weaker test of the semantics of subscribe/disconnect nested in sinks. let signal = Signal() var r = [Int]() var c: Connection? = nil var sink: ((Int) -> Void)! sink = { i in r.append(i) c?.disconnect() c = signal.subscribe(sink) } c = signal.subscribe(sink) for i in 1...6 { signal.send(i) } c?.disconnect() XCTAssertEqual(r, [1, 2, 3, 4, 5, 6]) } //MARK: Test reentrant sends func test_Reentrancy_SinksAreNeverNested() { let signal = Signal() var s = "" let c = signal.subscribe { i in s += " (\(i)" if i > 0 { signal.send(i - 1) // This send is asynchronous. The value is sent at the end of the outermost send. } s += ")" } signal.send(3) c.disconnect() XCTAssertEqual(s, " (3) (2) (1) (0)") } func test_Reentrancy_SinksReceiveAllValuesSentAfterTheyConnectedEvenWhenReentrant() { var s = "" let signal = Signal() // Let's do an exponential cascade of decrements with two sinks: var values1 = [Int]() let c1 = signal.subscribe { i in values1.append(i) s += " (\(i)" if i > 0 { signal.send(i - 1) } s += ")" } var values2 = [Int]() let c2 = signal.subscribe { i in values2.append(i) s += " (\(i)" if i > 0 { signal.send(i - 1) } s += ")" } signal.send(2) // There should be no nesting and both sinks should receive all sent values, in correct order. XCTAssertEqual(values1, [2, 1, 1, 0, 0, 0, 0]) XCTAssertEqual(values2, [2, 1, 1, 0, 0, 0, 0]) XCTAssertEqual(s, " (2) (2) (1) (1) (1) (1) (0) (0) (0) (0) (0) (0) (0) (0)") c1.disconnect() c2.disconnect() } func test_Reentrancy_SinksDoNotReceiveValuesSentToTheSignalBeforeTheyWereConnected() { let signal = Signal() var values1 = [Int]() var values2 = [Int]() var c2: Connection? = nil let c1 = signal.subscribe { i in values1.append(i) if i == 3 && c2 == nil { signal.send(0) // This should not reach c2 c2 = signal.subscribe { i in values2.append(i) } } } signal.send(1) signal.send(2) signal.send(3) signal.send(4) signal.send(5) XCTAssertEqual(values1, [1, 2, 3, 0, 4, 5]) XCTAssertEqual(values2, [4, 5]) c1.disconnect() c2?.disconnect() } //MARK: sendLater / sendNow func test_Reentrancy_SendLaterSendsValueLater() { let signal = Signal() var r = [Int]() let c = signal.subscribe { r.append($0) } signal.sendLater(0) signal.sendLater(1) signal.sendLater(2) XCTAssertEqual(r, []) signal.sendNow() XCTAssertEqual(r, [0, 1, 2]) c.disconnect() } func test_Reentrancy_SendLaterDoesntSendValueToSinksConnectedLater() { let signal = Signal() signal.sendLater(0) signal.sendLater(1) signal.sendLater(2) var r = [Int]() let c = signal.subscribe { r.append($0) } signal.sendLater(3) signal.sendLater(4) XCTAssertEqual(r, []) signal.sendNow() XCTAssertEqual(r, [3, 4]) c.disconnect() } func test_Reentrancy_SendLaterDoesntSendValueToSinksConnectedLaterEvenIfThereAreOtherSinks() { let signal = Signal() var r1 = [Int]() let c1 = signal.subscribe { r1.append($0) } signal.sendLater(0) signal.sendLater(1) signal.sendLater(2) var r2 = [Int]() let c2 = signal.subscribe { r2.append($0) } signal.sendLater(3) signal.sendLater(4) XCTAssertEqual(r1, []) XCTAssertEqual(r2, []) signal.sendNow() XCTAssertEqual(r1, [0, 1, 2, 3, 4]) XCTAssertEqual(r2, [3, 4]) c1.disconnect() c2.disconnect() } func test_Reentrancy_SendLaterUsingCounter() { let counter = Counter() var s = "" let c = counter.subscribe { value in s += " (\(value)" if value < 5 { counter.increment() } s += ")" } let v = counter.increment() XCTAssertEqual(v, 1) XCTAssertEqual(s, " (1) (2) (3) (4) (5)") c.disconnect() } } private class Counter: SourceType { typealias Value = Int private let lock = Lock() private var counter: Int = 0 private var signal = Signal() func add(_ sink: Sink) where Sink.Value == Int { signal.add(sink) } @discardableResult func remove(_ sink: Sink) -> Sink where Sink.Value == Int { return signal.remove(sink) } @discardableResult func increment() -> Int { let value: Int = lock.withLock { self.counter += 1 let v = self.counter signal.sendLater(v) return v } signal.sendNow() return value } } ================================================ FILE: Tests/GlueKitTests/SimpleSourcesTests.swift ================================================ // // SimpleSourcesTests.swift // GlueKit // // Created by Károly Lőrentey on 2015-12-03. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest import GlueKit class SimpleSourcesTests: XCTestCase { func testEmptySource() { let source = AnySource.empty() let sink = MockSink() source.add(sink) sink.expectingNothing { // Ah, uhm, not sure what to test here, really } source.remove(sink) } func testNeverSource() { let source = AnySource.never() let sink = MockSink() source.add(sink) sink.expectingNothing { // Ah, uhm, not sure what to test here, really } source.remove(sink) } func testJustSource() { let source = AnySource.just(42) let sink = MockSink() _ = sink.expecting(42) { source.add(sink) } source.remove(sink) } } ================================================ FILE: Tests/GlueKitTests/SourceOperatorTests.swift ================================================ // // SourceOperatorTests.swift // GlueKit // // Created by Károly Lőrentey on 2015-12-03. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest import GlueKit class SourceOperatorTests: XCTestCase { func testSourceOperatorIsCalledOnEachSinkDuringEachSend() { let signal = Signal() var count = 0 let source = signal.transform(Double.self) { input, sink in count += 1 } XCTAssertEqual(count, 0) let c1 = source.subscribe { _ in } let c2 = source.subscribe { _ in } XCTAssertEqual(count, 0) signal.send(10) XCTAssertEqual(count, 2) signal.send(20) XCTAssertEqual(count, 4) signal.send(30) XCTAssertEqual(count, 6) c1.disconnect() c2.disconnect() signal.send(40) XCTAssertEqual(count, 6) } func testSourceOperatorRetainsSource() { var source: AnySource? = nil weak var weakSignal: Signal? = nil do { let signal = Signal() weakSignal = signal source = signal.transform(Int.self) { input, sink in // Noop sink(input) } } XCTAssertNotNil(weakSignal) source = nil XCTAssertNil(weakSignal) noop(source) } func testSourceDoesntRetainOperator() { weak var weakResource: NSObject? = nil do { let resource = NSObject() weakResource = resource let source = Signal().transform(Int.self) { input, sink in noop(resource) sink(input) } XCTAssertNotNil(weakResource) noop(source) } XCTAssertNil(weakResource) } func testMap() { let signal = Signal() let source = signal.map { i in "\(i)" } let sink = MockSink() source.add(sink) sink.expecting("1") { signal.send(1) } sink.expecting("2") { signal.send(2) } sink.expecting("3") { signal.send(3) } source.remove(sink) } func testFilter() { let signal = Signal() let oddSource = signal.filter { $0 % 2 == 1 } let sink = MockSink() oddSource.add(sink) sink.expecting([1, 3, 5, 7, 9]) { (1...10).forEach { signal.send($0) } } oddSource.remove(sink) } func testOptionalFlatMap() { let signal = Signal() let source = signal.flatMap { i in i % 2 == 0 ? i / 2 : nil } let sink = MockSink() source.add(sink) sink.expecting([1, 2, 3, 4, 5]) { (1...10).forEach { signal.send($0) } } source.remove(sink) } func testArrayFlatMap() { let signal = Signal() let source = signal.flatMap { (i: Int) -> [Int] in if i > 0 { return (1...i).filter { i % $0 == 0 } } else { return [] } } // Source sends all divisors of all numbers sent by its input source. let sink = MockSink() source.add(sink) sink.expecting(1) { signal.send(1) } sink.expecting([1, 2]) { signal.send(2) } sink.expecting([1, 3]) { signal.send(3) } sink.expecting([1, 2, 4]) { signal.send(4) } sink.expecting([1, 5]) { signal.send(5) } sink.expecting([1, 2, 3, 6]) { signal.send(6) } sink.expecting([1, 7]) { signal.send(7) } sink.expecting([1, 2, 4, 8]) { signal.send(8) } sink.expecting([1, 3, 9]) { signal.send(9) } sink.expecting([1, 2, 5, 10]) { signal.send(10) } source.remove(sink) } } ================================================ FILE: Tests/GlueKitTests/TestChange.swift ================================================ // // TestChange.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-26. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest import GlueKit internal struct TestChange: ChangeType, Equatable, CustomStringConvertible { typealias Value = Int var values: [Int] init(_ values: [Int]) { self.values = values } init(from oldValue: Int, to newValue: Int) { values = [oldValue, newValue] } var isEmpty: Bool { return values.isEmpty } func apply(on value: inout Int) { XCTAssertEqual(value, values.first!) value = values.last! } mutating func merge(with next: TestChange) { XCTAssertEqual(self.values.last!, next.values.first!) values += next.values.dropFirst() } func reversed() -> TestChange { return TestChange(values.reversed()) } public var description: String { return values.map { "\($0)" }.joined(separator: " -> ") } static func ==(left: TestChange, right: TestChange) -> Bool { return left.values == right.values } } ================================================ FILE: Tests/GlueKitTests/TestObservable.swift ================================================ // // TestObservable.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-26. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest @testable import GlueKit class TestObservable: ObservableType, TransactionalThing { typealias Change = TestChange typealias Value = Int var _signal: TransactionalSignal? = nil var _transactionCount: Int = 0 var _value: Value init(_ value: Value) { self._value = value } var value: Value { get { return _value } set { let old = _value beginTransaction() _value = newValue sendChange(.init(from: old, to: _value)) endTransaction() } } func add(_ sink: Sink) where Sink.Value == Update { signal.add(sink) } func remove(_ sink: Sink) -> Sink where Sink.Value == Update { return signal.remove(sink) } } class TestObservableValue: ObservableValueType, TransactionalThing { typealias Change = ValueChange var _signal: TransactionalSignal>? = nil var _transactionCount: Int = 0 var _value: Value init(_ value: Value) { self._value = value } var value: Value { get { return _value } set { let old = _value beginTransaction() _value = newValue sendChange(.init(from: old, to: _value)) endTransaction() } } func add(_ sink: Sink) where Sink.Value == Update { signal.add(sink) } func remove(_ sink: Sink) -> Sink where Sink.Value == Update { return signal.remove(sink) } } ================================================ FILE: Tests/GlueKitTests/TestUpdatable.swift ================================================ // // TestUpdatable.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-26. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest @testable import GlueKit class TestUpdatable: UpdatableType, TransactionalThing { typealias Change = TestChange typealias Value = Int var _signal: TransactionalSignal? = nil var _transactionCount: Int = 0 var _value: Value init(_ value: Value) { self._value = value } var value: Value { get { return _value } set { let old = _value beginTransaction() _value = newValue sendChange(.init(from: old, to: _value)) endTransaction() } } func apply(_ update: Update) { switch update { case .beginTransaction: beginTransaction() case .change(let change): change.apply(on: &_value) sendChange(change) case .endTransaction: endTransaction() } } func add(_ sink: Sink) where Sink.Value == Update { signal.add(sink) } func remove(_ sink: Sink) -> Sink where Sink.Value == Update { return signal.remove(sink) } } class TestUpdatableValue: UpdatableValueType, TransactionalThing { typealias Change = ValueChange var _signal: TransactionalSignal>? = nil var _transactionCount: Int = 0 var _value: Value init(_ value: Value) { self._value = value } var value: Value { get { return _value } set { let old = _value beginTransaction() _value = newValue sendChange(.init(from: old, to: _value)) endTransaction() } } func apply(_ update: Update) { switch update { case .beginTransaction: beginTransaction() case .change(let change): change.apply(on: &_value) sendChange(change) case .endTransaction: endTransaction() } } func add(_ sink: Sink) where Sink.Value == Update { signal.add(sink) } func remove(_ sink: Sink) -> Sink where Sink.Value == Update { return signal.remove(sink) } } ================================================ FILE: Tests/GlueKitTests/TestUtilities.swift ================================================ // // TestUtilities.swift // GlueKit // // Created by Károly Lőrentey on 2015-12-03. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest import Foundation @testable import GlueKit @inline(never) func noop(_ value: Value) { } func XCTAssertEqual(_ a: @autoclosure () -> [[E]], _ b: @autoclosure () -> [[E]], message: String? = nil, file: StaticString = #file, line: UInt = #line) { let av = a() let bv = b() if !av.elementsEqual(bv, by: ==) { XCTFail(message ?? "\(av) is not equal to \(bv)", file: file, line: line) } } ================================================ FILE: Tests/GlueKitTests/TimerSourceTests.swift ================================================ // // TimerSourceTests.swift // GlueKit // // Created by Károly Lőrentey on 2015-12-03. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest @testable import GlueKit class TimerSourceTests: XCTestCase { func testNeverFiringTimer() { let queue = DispatchQueue(label: "testqueue", attributes: []) var timerTimes = [Date]() let timerSemaphore = DispatchSemaphore(value: 0) let source = TimerSource(queue: queue) { timerTimes.append(Date()) timerSemaphore.signal() return nil } XCTAssertEqual(timerTimes, []) var sinkTimes = [Date]() let sinkSemaphore = DispatchSemaphore(value: 0) let connection = source.subscribe { sinkTimes.append(Date()) sinkSemaphore.signal() } XCTAssertEqual(timerSemaphore.wait(timeout: DispatchTime.now() + 3.0), .success, "Timer source should call timer closure when it is first connected") XCTAssertEqual(timerTimes.count, 1) XCTAssertEqual(sinkTimes, []) connection.disconnect() } func testRefreshingTimerSource() { let queue = DispatchQueue(label: "testqueue", attributes: []) var signal = false var timerTimes = [Date]() let timerSemaphore = DispatchSemaphore(value: 0) var triggerDate: Date? = nil let source = TimerSource(queue: queue) { timerTimes.append(Date()) if let date = triggerDate { triggerDate = nil return date } else { if signal { timerSemaphore.signal() } return nil } } XCTAssertEqual(timerTimes, []) var sinkTimes = [Date]() let connection = source.subscribe { sinkTimes.append(Date()) } // Timer should have returned nil -> No firing yet XCTAssertEqual(sinkTimes, []) queue.sync { signal = true triggerDate = Date(timeIntervalSinceNow: 0.1) source.start() } // Timer should return non-nil, clear triggerDate and signal the semaphore. XCTAssertEqual(.success, timerSemaphore.wait(timeout: DispatchTime.now() + 3.0)) XCTAssertEqual(timerTimes.count, 3) // 1: subscribe, 2: start, 3: after first firing XCTAssertEqual(sinkTimes.count, 1) // Should fire only once connection.disconnect() } func testSimplePeriodicSignal() { let queue = DispatchQueue(label: "testqueue", attributes: []) let start = Date().addingTimeInterval(0.2) let interval: TimeInterval = 0.2 let source = TimerSource(queue: queue, start: start, interval: interval) var ticks = [TimeInterval]() var count = 0 let sem = DispatchSemaphore(value: 0) let connection = source.subscribe { i in let elapsed = Date().timeIntervalSince(start) ticks.append(elapsed) NSLog("tick \(count) at \(elapsed)") count += 1 if count >= 3 { sem.signal() } } XCTAssertEqual(.success, sem.wait(timeout: DispatchTime.now() + 3.0)) connection.disconnect() let diffs: [TimeInterval] = ticks.enumerated().map { tick, elapsed in let ideal = TimeInterval(tick) * interval return elapsed - ideal } XCTAssertEqual(diffs.filter { $0 < 0 }, []) } } ================================================ FILE: Tests/GlueKitTests/TransactionStateTests.swift ================================================ // // TransactionalTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-26. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest @testable import GlueKit private class TransactionalTestObservable: ObservableValueType, TransactionalThing { typealias Value = Int typealias Change = ValueChange var _signal: TransactionalSignal>? = nil var _transactionCount: Int = 0 var _value: Value = 0 var value: Value { get { return _value } set { beginTransaction() let old = _value _value = newValue sendChange(.init(from: old, to: _value)) endTransaction() } } func add(_ sink: Sink) where Sink.Value == Update { signal.add(sink) } func remove(_ sink: Sink) -> Sink where Sink.Value == Update { return signal.remove(sink) } var activated = 0 var deactivated = 0 func activate() { activated += 1 } func deactivate() { deactivated += 1 } } class TransactionalTests: XCTestCase { func testUpdatesSourceRemainsTheSame1() { let observable = TransactionalTestObservable() let sink1 = MockValueUpdateSink() let updates1 = observable.updates updates1.add(sink1) let sink2 = MockValueUpdateSink() let updates2 = observable.updates updates2.add(sink2) // We can't compare the sources, but we can check that triggering // a change is reported from both of them. sink1.expecting(["begin", "0 -> 1", "end"]) { sink2.expecting(["begin", "0 -> 1", "end"]) { observable.value = 1 } } updates1.remove(sink1) updates2.remove(sink2) } func testUpdatesSourceRemainsTheSame2() { let observable = TransactionalTestObservable() let sink1 = MockValueUpdateSink() observable.updates.add(sink1) let sink2 = MockValueUpdateSink() observable.updates.add(sink2) // We can't compare the sources, but we can check that triggering // a change is reported from both of them. sink1.expecting(["begin", "0 -> 1", "end"]) { sink2.expecting(["begin", "0 -> 1", "end"]) { observable.value = 1 } } observable.updates.remove(sink1) observable.updates.remove(sink2) } func testSendIfConnected() { let observable = TransactionalTestObservable() // The autoclosure argument should not be called if the observable isn't connected. observable.beginTransaction() observable.sendIfConnected({ XCTFail(); return ValueChange(from: 0, to: 1) }()) observable.endTransaction() let sink = MockValueUpdateSink() observable.updates.add(sink) sink.expecting(["begin", "0 -> 1", "end"]) { observable.beginTransaction() observable.sendIfConnected(ValueChange(from: 0, to: 1)) observable.endTransaction() } observable.updates.remove(sink) } func testSendLater() { let observable = TransactionalTestObservable() let sink = MockValueUpdateSink() observable.updates.add(sink) sink.expecting("begin") { observable.beginTransaction() } observable.signal.sendLater(.change(ValueChange(from: 0, to: 1))) sink.expecting("0 -> 1") { observable.signal.sendNow() } sink.expecting("end") { observable.endTransaction() } observable.updates.remove(sink) } func testSendUpdate() { let observable = TransactionalTestObservable() let sink = MockValueUpdateSink() observable.updates.add(sink) sink.expecting("begin") { observable.send(.beginTransaction) } sink.expecting("0 -> 1") { observable.send(.change(ValueChange(from: 0, to: 1))) } sink.expecting("end") { observable.send(.endTransaction) } observable.updates.remove(sink) } func testSubscribingToOpenTransaction() { let observable = TransactionalTestObservable() let sink = MockValueUpdateSink() observable.beginTransaction() sink.expecting("begin") { observable.updates.add(sink) } sink.expecting("end") { observable.endTransaction() } _ = observable.updates.remove(sink) } func testUnsubscribingFromOpenTransaction() { let observable = TransactionalTestObservable() let sink = MockValueUpdateSink() observable.updates.add(sink) sink.expecting("begin") { observable.beginTransaction() } sink.expecting("end") { _ = observable.updates.remove(sink) } observable.endTransaction() } func testPropertiesWithNestedTransactions() { let observable = TransactionalTestObservable() XCTAssertEqual(observable.isInTransaction, false) XCTAssertEqual(observable.isConnected, false) XCTAssertEqual(observable.isActive, false) // Start an outer transaction. observable.beginTransaction() XCTAssertEqual(observable.isInTransaction, true) XCTAssertEqual(observable.isConnected, false) XCTAssertEqual(observable.isActive, true) // Start a nested transaction. observable.beginTransaction() XCTAssertEqual(observable.isInTransaction, true) XCTAssertEqual(observable.isConnected, false) XCTAssertEqual(observable.isActive, true) observable.endTransaction() XCTAssertEqual(observable.isInTransaction, true) XCTAssertEqual(observable.isConnected, false) XCTAssertEqual(observable.isActive, true) observable.endTransaction() XCTAssertEqual(observable.isInTransaction, false) XCTAssertEqual(observable.isConnected, false) XCTAssertEqual(observable.isActive, false) let sink = MockValueUpdateSink() observable.updates.add(sink) XCTAssertEqual(observable.isInTransaction, false) XCTAssertEqual(observable.isConnected, true) XCTAssertEqual(observable.isActive, true) sink.expecting("begin") { observable.beginTransaction() } XCTAssertEqual(observable.isInTransaction, true) XCTAssertEqual(observable.isConnected, true) XCTAssertEqual(observable.isActive, true) sink.expecting("end") { observable.endTransaction() } XCTAssertEqual(observable.isInTransaction, false) XCTAssertEqual(observable.isConnected, true) XCTAssertEqual(observable.isActive, true) observable.updates.remove(sink) XCTAssertEqual(observable.isInTransaction, false) XCTAssertEqual(observable.isConnected, false) XCTAssertEqual(observable.isActive, false) } func testActivation() { let observable = TransactionalTestObservable() let sink = MockValueUpdateSink() XCTAssertEqual(observable.activated, 0) observable.updates.add(sink) XCTAssertEqual(observable.activated, 1) XCTAssertEqual(observable.deactivated, 0) observable.updates.remove(sink) XCTAssertEqual(observable.deactivated, 1) } } class TestTransactionalSource: TransactionalSource> { var activated = 0 var deactivated = 0 override func activate() { super.activate() activated += 1 } override func deactivate() { super.deactivate() deactivated += 1 } } class TransactionSourceTests: XCTestCase { func test() { let source = TestTransactionalSource() XCTAssertEqual(source.activated, 0) XCTAssertEqual(source.deactivated, 0) let sink = MockValueUpdateSink() source.add(sink) XCTAssertEqual(source.activated, 1) XCTAssertEqual(source.deactivated, 0) source.remove(sink) XCTAssertEqual(source.activated, 1) XCTAssertEqual(source.deactivated, 1) } } ================================================ FILE: Tests/GlueKitTests/TwoWayBindingTests.swift ================================================ // // UpdatableTests.swift // GlueKit // // Created by Károly Lőrentey on 2015-12-07. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest import GlueKit class UpdatableTests: XCTestCase { func test_bind_OneWayBinding() { let master = Variable(0) let slave = Variable(100) let c = master.subscribe(to: slave) XCTAssertEqual(slave.value, 0) master.value = 1 XCTAssertEqual(master.value, 1) XCTAssertEqual(slave.value, 1) slave.value = 200 XCTAssertEqual(master.value, 1, "Connection should not be a two-way binding") XCTAssertEqual(slave.value, 200) master.value = 2 XCTAssertEqual(master.value, 2) XCTAssertEqual(slave.value, 2) c.disconnect() master.value = 3 XCTAssertEqual(master.value, 3) XCTAssertEqual(slave.value, 2) } func test_bind_TwoWayBinding() { let master = Variable(0) let slave = Variable(1) let c = master.bind(to: slave) XCTAssertEqual(master.value, 0) // Slave should get the value of master XCTAssertEqual(slave.value, 0) master.value = 1 XCTAssertEqual(master.value, 1) XCTAssertEqual(slave.value, 1) slave.value = 2 XCTAssertEqual(master.value, 2) XCTAssertEqual(slave.value, 2) c.disconnect() // The variables should now be independent again. master.value = 3 XCTAssertEqual(master.value, 3) XCTAssertEqual(slave.value, 2) slave.value = 4 XCTAssertEqual(master.value, 3) XCTAssertEqual(slave.value, 4) } func test_Connector_bind() { let master = Variable(0) let slave = Variable(1) let connector = Connector() connector.bind(master, to: slave) XCTAssertEqual(master.value, 0) // Slave should get the value of master XCTAssertEqual(slave.value, 0) master.value = 1 XCTAssertEqual(master.value, 1) XCTAssertEqual(slave.value, 1) slave.value = 2 XCTAssertEqual(master.value, 2) XCTAssertEqual(slave.value, 2) connector.disconnect() // The variables should now be independent again. master.value = 3 XCTAssertEqual(master.value, 3) XCTAssertEqual(slave.value, 2) slave.value = 4 XCTAssertEqual(master.value, 3) XCTAssertEqual(slave.value, 4) } func test_updates_masterTransaction() { let master = Variable(0) let slave = Variable(1) let msink = MockValueUpdateSink(master) let ssink = MockValueUpdateSink(slave) let c = msink.expecting(["begin", "end"]) { ssink.expecting(["begin", "1 -> 0", "end"]) { return master.bind(to: slave) } } msink.expecting("begin") { ssink.expecting("begin") { master.apply(.beginTransaction) } } msink.expecting("0 -> 2") { ssink.expecting("0 -> 2") { master.value = 2 } } msink.expecting("end") { ssink.expecting("end") { master.apply(.endTransaction) } } msink.expectingNothing { ssink.expectingNothing { c.disconnect() } } ssink.disconnect() msink.disconnect() } func test_updates_slaveTransaction() { let master = Variable(0) let slave = Variable(1) let msink = MockValueUpdateSink(master) let ssink = MockValueUpdateSink(slave) let c = msink.expecting(["begin", "end"]) { ssink.expecting(["begin", "1 -> 0", "end"]) { return master.bind(to: slave) } } msink.expecting("begin") { ssink.expecting("begin") { slave.apply(.beginTransaction) } } msink.expecting("0 -> 2") { ssink.expecting("0 -> 2") { slave.value = 2 } } msink.expecting("end") { ssink.expecting("end") { slave.apply(.endTransaction) } } msink.expectingNothing { ssink.expectingNothing { c.disconnect() } } ssink.disconnect() msink.disconnect() } #if false // Binding/unbinding during transactions is currently unsupported. func test_updates_bindingDuringMasterTransaction() { let master = Variable(0) let slave = Variable(1) let sink = MockValueUpdateSink(slave) master.apply(.beginTransaction) let c = sink.expecting(["begin", "1 -> 0"]) { return master.bind(to: slave) } sink.expecting("0 -> 2") { master.value = 2 } sink.expecting("end") { master.apply(.endTransaction) } sink.expectingNothing { c.disconnect() } sink.disconnect() } func test_updates_bindingDuringSinkTransaction() { let master = Variable(0) let slave = Variable(1) let sink = MockValueUpdateSink(slave) sink.expecting("begin") { slave.apply(.beginTransaction) } let c = sink.expecting(["1 -> 0"]) { return master.bind(to: slave) } sink.expecting("0 -> 2") { master.value = 2 } sink.expectingNothing { master.apply(.endTransaction) } sink.expectingNothing { c.disconnect() } sink.expecting("end") { slave.apply(.endTransaction) } sink.disconnect() } func test_updates_unbindingDuringMasterTransaction() { let master = Variable(0) let slave = Variable(1) let sink = MockValueUpdateSink(slave) let c = sink.expecting(["begin", "0 -> 1", "end"]) { return master.bind(to: slave) } sink.expecting("begin") { master.apply(.beginTransaction) } sink.expecting("1 -> 2") { master.value = 2 } sink.expecting("end") { c.disconnect() } sink.expectingNothing { master.apply(.endTransaction) } sink.disconnect() } func test_updates_unbindingDuringSinkTransaction() { let master = Variable(0) let slave = Variable(1) let sink = MockValueUpdateSink(slave) let c = sink.expecting(["begin", "0 -> 1", "end"]) { return master.bind(to: slave) } sink.expecting("begin") { slave.apply(.beginTransaction) } sink.expecting("1 -> 2") { master.value = 2 } sink.expectingNothing { c.disconnect() } sink.expectingNothing { master.apply(.endTransaction) } sink.expecting("end") { slave.apply(.endTransaction) } sink.disconnect() } #endif } ================================================ FILE: Tests/GlueKitTests/TypeHelperTests.swift ================================================ // // TypeHelperTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-10. // Copyright © 2015–2017 Károly Lőrentey. // import Foundation import XCTest import GlueKit private class KVOTest: NSObject { @objc dynamic var string: String = "" @objc dynamic var bool: Bool = false @objc dynamic var int: Int = 0 @objc dynamic var float: Float = 0 @objc dynamic var double: Double = 0 @objc dynamic var cgFloat: CGFloat = 0 @objc dynamic var point: CGPoint = .init(x: 0, y: 0) @objc dynamic var size: CGSize = .init(width: 0, height: 0) @objc dynamic var rect: CGRect = .init(x: 0, y: 0, width: 0, height: 0) @objc dynamic var transform: CGAffineTransform = .identity } class TypeHelperTests: XCTestCase { private func check( type: V.Type = V.self, key: String, sourceTx: (AnySource) -> AnySource, observableTx: (AnyObservableValue) -> AnyObservableValue, updatableTx: (AnyUpdatableValue) -> AnyUpdatableValue, getter: (KVOTest) -> V, setter: (KVOTest, V) -> (), value0: V, value1: V, value2: V) { let t = KVOTest() setter(t, value0) let source = sourceTx(t.glue.observable(forKeyPath: key).futureValues) let observable = observableTx(t.glue.observable(forKeyPath: key)) let updatable = updatableTx(t.glue.updatable(forKey: key)) XCTAssertEqual(observable.value, value0) XCTAssertEqual(updatable.value, value0) let smock = MockSink(source) let omock = MockValueUpdateSink(observable) let umock = MockValueUpdateSink(updatable) smock.expecting(value1) { omock.expecting(["begin", "\(value0) -> \(value1)", "end"]) { umock.expecting(["begin", "\(value0) -> \(value1)", "end"]) { setter(t, value1) } } } XCTAssertEqual(observable.value, value1) XCTAssertEqual(updatable.value, value1) smock.expecting(value2) { omock.expecting(["begin", "\(value1) -> \(value2)", "end"]) { umock.expecting(["begin", "\(value1) -> \(value2)", "end"]) { updatable.value = value2 } } } XCTAssertEqual(observable.value, value2) XCTAssertEqual(updatable.value, value2) XCTAssertEqual(getter(t), value2) } func testForceCasting() { check(key: "string", sourceTx: { $0.forceCasted(to: NSString.self) }, observableTx: { $0.forceCasted(to: NSString.self) }, updatableTx: { $0.forceCasted(to: NSString.self) }, getter: { $0.string as NSString }, setter: { $0.string = $1 as String }, value0: "foo", value1: "bar", value2: "baz") check(key: "int", sourceTx: { $0.forceCasted(to: NSNumber.self) }, observableTx: { $0.forceCasted(to: NSNumber.self) }, updatableTx: { $0.forceCasted(to: NSNumber.self) }, getter: { NSNumber(value: $0.int) }, setter: { $0.int = $1.intValue }, value0: NSNumber(value: 2), value1: NSNumber(value: 3), value2: NSNumber(value: 4)) } func testString() { check(key: "string", sourceTx: { $0.asString }, observableTx: { $0.asString }, updatableTx: { $0.asString }, getter: { $0.string }, setter: { $0.string = $1 }, value0: "foo", value1: "bar", value2: "baz") } func testBool() { check(key: "bool", sourceTx: { $0.asBool }, observableTx: { $0.asBool }, updatableTx: { $0.asBool }, getter: { $0.bool }, setter: { $0.bool = $1 }, value0: false, value1: true, value2: false) } func testInt() { check(key: "int", sourceTx: { $0.asInt }, observableTx: { $0.asInt }, updatableTx: { $0.asInt }, getter: { $0.int }, setter: { $0.int = $1 }, value0: 1, value1: 2, value2: 3) } func testFloat() { check(key: "float", sourceTx: { $0.asFloat }, observableTx: { $0.asFloat }, updatableTx: { $0.asFloat }, getter: { $0.float }, setter: { $0.float = $1 }, value0: 1, value1: 2, value2: 3) check(key: "int", sourceTx: { $0.asFloat }, observableTx: { $0.asFloat }, updatableTx: { $0.asFloat }, getter: { Float($0.int) }, setter: { $0.int = Int($1) }, value0: 1, value1: 2, value2: 3) check(key: "double", sourceTx: { $0.asFloat }, observableTx: { $0.asFloat }, updatableTx: { $0.asFloat }, getter: { Float($0.double) }, setter: { $0.double = Double($1) }, value0: 1, value1: 2, value2: 3) } func testDouble() { check(key: "double", sourceTx: { $0.asDouble }, observableTx: { $0.asDouble }, updatableTx: { $0.asDouble }, getter: { $0.double }, setter: { $0.double = $1 }, value0: 1, value1: 2, value2: 3) check(key: "float", sourceTx: { $0.asDouble }, observableTx: { $0.asDouble }, updatableTx: { $0.asDouble }, getter: { Double($0.float) }, setter: { $0.float = Float($1) }, value0: 1, value1: 2, value2: 3) check(key: "int", sourceTx: { $0.asDouble }, observableTx: { $0.asDouble }, updatableTx: { $0.asDouble }, getter: { Double($0.int) }, setter: { $0.int = Int($1) }, value0: 1, value1: 2, value2: 3) } func testCGFloat() { check(key: "cgFloat", sourceTx: { $0.asCGFloat }, observableTx: { $0.asCGFloat }, updatableTx: { $0.asCGFloat }, getter: { $0.cgFloat }, setter: { $0.cgFloat = $1 }, value0: 1, value1: 2, value2: 3) check(key: "int", sourceTx: { $0.asCGFloat }, observableTx: { $0.asCGFloat }, updatableTx: { $0.asCGFloat }, getter: { CGFloat($0.int) }, setter: { $0.int = Int($1) }, value0: 1, value1: 2, value2: 3) check(key: "float", sourceTx: { $0.asCGFloat }, observableTx: { $0.asCGFloat }, updatableTx: { $0.asCGFloat }, getter: { CGFloat($0.float) }, setter: { $0.float = Float($1) }, value0: 1, value1: 2, value2: 3) } func testCGPoint() { check(key: "point", sourceTx: { $0.asCGPoint }, observableTx: { $0.asCGPoint }, updatableTx: { $0.asCGPoint }, getter: { $0.point }, setter: { $0.point = $1 }, value0: CGPoint(x: 1, y: 2), value1: CGPoint(x: 3, y: 4), value2: CGPoint(x: 5, y: 6)) } func testCGSize() { check(key: "size", sourceTx: { $0.asCGSize }, observableTx: { $0.asCGSize }, updatableTx: { $0.asCGSize }, getter: { $0.size }, setter: { $0.size = $1 }, value0: CGSize(width: 1, height: 2), value1: CGSize(width: 3, height: 4), value2: CGSize(width: 5, height: 6)) } func testCGRect() { check(key: "rect", sourceTx: { $0.asCGRect }, observableTx: { $0.asCGRect }, updatableTx: { $0.asCGRect }, getter: { $0.rect }, setter: { $0.rect = $1 }, value0: CGRect(x: 1, y: 2, width: 3, height: 4), value1: CGRect(x: 5, y: 6, width: 7, height: 8), value2: CGRect(x: 9, y: 10, width: 11, height: 12)) } func testCGAffineTransform() { check(key: "transform", sourceTx: { $0.asCGAffineTransform }, observableTx: { $0.asCGAffineTransform }, updatableTx: { $0.asCGAffineTransform }, getter: { $0.transform }, setter: { $0.transform = $1 }, value0: CGAffineTransform(rotationAngle: CGFloat.pi / 4), value1: CGAffineTransform(scaleX: 0.4, y: 0.6), value2: CGAffineTransform(translationX: 100, y: 200)) } } ================================================ FILE: Tests/GlueKitTests/UpdatableValueTests.swift ================================================ // // UpdatableValueTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-27. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest @testable import GlueKit class UpdatableValueTests: XCTestCase { func test_anyUpdatable_fromUpdatableValue() { let test = TestUpdatableValue(0) let any = test.anyUpdatableValue XCTAssertEqual(any.value, 0) let updateSink = MockValueUpdateSink(any.updates) let changeSink = TransformedMockSink, String>({ "\($0.old) -> \($0.new)" }) changeSink.subscribe(to: any.changes) let valuesSink = MockSink() valuesSink.expecting(0) { valuesSink.subscribe(to: any.values) } let futureValuesSink = MockSink(any.futureValues) updateSink.expecting("begin") { test.beginTransaction() } updateSink.expecting("0 -> 1") { test.value = 1 } updateSink.expecting("end") { changeSink.expecting("0 -> 1") { valuesSink.expecting(1) { futureValuesSink.expecting(1) { test.endTransaction() } } } } updateSink.expecting(["begin", "1 -> 2", "end"]) { changeSink.expecting("1 -> 2") { valuesSink.expecting(2) { futureValuesSink.expecting(2) { any.value = 2 } } } } updateSink.expecting(["begin", "end"]) { any.withTransaction {} } updateSink.expecting(["begin", "2 -> 3", "3 -> 4", "end"]) { changeSink.expecting("2 -> 4") { valuesSink.expecting(4) { futureValuesSink.expecting(4) { any.withTransaction { any.value = 3 any.value = 4 } } } } } } func test_anyUpdatable_fromClosures() { var value = 0 let signal = Signal>() var transactions = 0 let begin = { transactions += 1 if transactions == 1 { signal.send(.beginTransaction) } } let end = { transactions -= 1 if transactions == 0 { signal.send(.endTransaction) } } let test = AnyUpdatableValue( getter: { value }, apply: { (update: ValueUpdate) -> Void in switch update { case .beginTransaction: begin() case .change(let change): value = change.new signal.send(update) case .endTransaction: end() } }, updates: signal.anySource) let any = test.anyUpdatableValue XCTAssertEqual(any.value, 0) let updateSink = MockValueUpdateSink(any.updates) let changeSink = TransformedMockSink, String>({ "\($0.old) -> \($0.new)" }) changeSink.subscribe(to: any.changes) let valuesSink = MockSink() valuesSink.expecting(0) { valuesSink.subscribe(to: any.values) } let futureValuesSink = MockSink(any.futureValues) updateSink.expecting("begin") { begin() } updateSink.expecting("0 -> 1") { test.value = 1 } updateSink.expecting("end") { changeSink.expecting("0 -> 1") { valuesSink.expecting(1) { futureValuesSink.expecting(1) { end() } } } } updateSink.expecting(["begin", "1 -> 2", "end"]) { changeSink.expecting("1 -> 2") { valuesSink.expecting(2) { futureValuesSink.expecting(2) { any.value = 2 } } } } updateSink.expecting(["begin", "end"]) { any.withTransaction {} } updateSink.expecting(["begin", "2 -> 3", "3 -> 4", "end"]) { changeSink.expecting("2 -> 4") { valuesSink.expecting(4) { futureValuesSink.expecting(4) { any.withTransaction { any.value = 3 any.value = 4 } } } } } } func test_anyObservable_fromAnyUpdatable() { let test = TestUpdatableValue(0) let any = test.anyUpdatableValue.anyObservableValue XCTAssertEqual(any.value, 0) let updateSink = MockValueUpdateSink(any.updates) updateSink.expecting("begin") { test.beginTransaction() } updateSink.expecting("0 -> 1") { test.value = 1 } updateSink.expecting("end") { test.endTransaction() } } } ================================================ FILE: Tests/GlueKitTests/UpdateTests.swift ================================================ // // UpdateTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-26. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest import GlueKit class UpdateTests: XCTestCase { func testChangeExtraction() { let change = TestChange([1, 2, 3]) let updates: [Update] = [ .beginTransaction, .change(change), .endTransaction, ] let expected: [String] = ["nil", "1 -> 2 -> 3", "nil"] let actual = updates.map { update -> String in if let change = update.change { return "\(change)" } else { return "nil" } } XCTAssertEqual(actual, expected) } func testFilter() { let change = TestChange([1, 2, 3]) let updates: [Update] = [ .beginTransaction, .change(change), .endTransaction, ] let expected1: [String] = ["begin", "1 -> 2 -> 3", "end"] let actual1 = updates.map { describe($0.filter { _ in true }) } XCTAssertEqual(actual1, expected1) let expected2: [String] = ["begin", "nil", "end"] let actual2 = updates.map { describe($0.filter { _ in false }) } XCTAssertEqual(actual2, expected2) } func testMap() { let change = TestChange([1, 2, 3]) let updates: [Update] = [ .beginTransaction, .change(change), .endTransaction, ] let expected: [String] = ["begin", "2 -> 4 -> 6", "end"] let actual = updates.map { describe($0.map { TestChange($0.values.map { 2 * $0 }) }) } XCTAssertEqual(actual, expected) } } ================================================ FILE: Tests/GlueKitTests/ValueBufferingTests.swift ================================================ // // BufferedValueTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-28. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest @testable import GlueKit class BufferedValueTests: XCTestCase { func test_connectsImmediately() { let observable = TestObservableValue(0) do { let buffered = observable.buffered() XCTAssertTrue(observable.isConnected) withExtendedLifetime(buffered) {} } XCTAssertFalse(observable.isConnected) } func test_isntRetainedByObservable() { let observable = TestObservableValue(0) weak var weakSink: MockValueUpdateSink? = nil do { let buffered = observable.buffered() let sink = MockValueUpdateSink() weakSink = sink sink.subscribe(to: buffered.updates) withExtendedLifetime(buffered) {} } // If the sink is still alive, the buffered observable wasn't deallocated. XCTAssertNil(weakSink, "Possible retain cycle") } func test_updates() { let observable = TestObservableValue(0) let buffered = observable.buffered() XCTAssertEqual(buffered.value, 0) observable.value = 1 XCTAssertEqual(buffered.value, 1) let sink = MockValueUpdateSink(buffered) sink.expecting(["begin", "1 -> 2", "end"]) { observable.value = 2 } sink.expecting("begin") { observable.beginTransaction() } sink.expectingNothing { observable.value = 3 } sink.expectingNothing { observable.value = 4 } sink.expecting(["2 -> 4", "end"]) { observable.endTransaction() } sink.disconnect() } } ================================================ FILE: Tests/GlueKitTests/ValueChangeTests.swift ================================================ // // ValueChangeTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-10-27. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest import GlueKit class ValueChangeTests: XCTestCase { func test() { let change = ValueChange(from: 1, to: 2) XCTAssertEqual(change.old, 1) XCTAssertEqual(change.new, 2) XCTAssertFalse(change.isEmpty) var value = 1 change.apply(on: &value) XCTAssertEqual(value, 2) XCTAssertEqual(change.applied(on: 1), 2) var m = ValueChange(from: 0, to: 1) m.merge(with: change) XCTAssertEqual(m.old, 0) XCTAssertEqual(m.new, 2) let m2 = ValueChange(from: 0, to: 1).merged(with: change) XCTAssertEqual(m2.old, 0) XCTAssertEqual(m2.new, 2) let reversed = change.reversed() XCTAssertEqual(reversed.old, 2) XCTAssertEqual(reversed.new, 1) let transformed = change.map { 2 * $0 } XCTAssertEqual(transformed.old, 2) XCTAssertEqual(transformed.new, 4) XCTAssertTrue(change == ValueChange(from: 1, to: 2)) XCTAssertFalse(change == ValueChange(from: 0, to: 2)) XCTAssertFalse(change == ValueChange(from: 1, to: 4)) XCTAssertFalse(change != ValueChange(from: 1, to: 2)) XCTAssertTrue(change != ValueChange(from: 0, to: 2)) XCTAssertTrue(change != ValueChange(from: 1, to: 4)) } } ================================================ FILE: Tests/GlueKitTests/ValueMappingTests.swift ================================================ // // SelectOperatorTests.swift // GlueKit // // Created by Károly Lőrentey on 2015-12-06. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest import GlueKit private class Book { let title: Variable let authors: SetVariable let chapters: ArrayVariable init(_ title: String, authors: Set = [], chapters: [String] = []) { self.title = .init(title) self.authors = .init(authors) self.chapters = .init(chapters) } } class ValueMappingTests: XCTestCase { func test_value() { let book = Book("foo") let title = book.title.map { $0.uppercased() } XCTAssertEqual(title.value, "FOO") let mock = MockValueUpdateSink(title) mock.expecting(["begin", "FOO -> BAR", "end"]) { book.title.value = "bar" } XCTAssertEqual(title.value, "BAR") } func test_updatableValue() { let book = Book("foo") // This is a simple mapping that ignores that a book's title is itself observable. let title = book.title.map({ $0.uppercased() }, inverse: { $0.lowercased() }) XCTAssertEqual(title.value, "FOO") let mock = MockValueUpdateSink(title) mock.expecting(["begin", "FOO -> BAR", "end"]) { book.title.value = "bar" } XCTAssertEqual(title.value, "BAR") mock.expecting(["begin", "BAR -> BAZ", "end"]) { title.value = "BAZ" } XCTAssertEqual(title.value, "BAZ") XCTAssertEqual(book.title.value, "baz") } func test_sourceField() { let b1 = Book("foo") let v = Variable(b1) let titleChanges = v.map { $0.title.changes } var expected: [ValueChange] = [] var actual: [ValueChange] = [] let connection = titleChanges.subscribe { change in actual.append(change) } func expect(_ change: ValueChange? = nil, file: StaticString = #file, line: UInt = #line, body: () -> ()) { if let change = change { expected.append(change) } body() if !expected.elementsEqual(actual, by: ==) { XCTFail("\(actual) is not equal to \(expected)", file: file, line: line) } expected = [] actual = [] } expect(ValueChange(from: "foo", to: "bar")) { b1.title.value = "bar" } let b2 = Book("fred") expect() { v.value = b2 } expect(ValueChange(from: "fred", to: "barney")) { b2.title.value = "barney" } connection.disconnect() } func test_valueField() { let book = Book("book") let v = Variable(book) let title = v.map{ $0.title.map { $0.uppercased() } } // The title is updatable; uppercasing it makes it observable only. XCTAssertEqual(title.value, "BOOK") let mock = MockValueUpdateSink(title) mock.expecting(["begin", "BOOK -> UPDATED", "end"]) { book.title.value = "updated" } XCTAssertEqual(title.value, "UPDATED") let book2 = Book("other") mock.expecting(["begin", "UPDATED -> OTHER", "end"]) { v.value = book2 } XCTAssertEqual(title.value, "OTHER") } func test_updatableField() { let book = Book("book") let v = Variable(book) let title = v.map{$0.title} XCTAssertEqual(title.value, "book") let mock = MockValueUpdateSink(title) mock.expecting(["begin", "book -> updated", "end"]) { title.value = "updated" } XCTAssertEqual(title.value, "updated") XCTAssertEqual(book.title.value, "updated") let book2 = Book("other") mock.expecting(["begin", "updated -> other", "end"]) { v.value = book2 } XCTAssertEqual(title.value, "other") } func test_arrayField() { let book = Book("book", chapters: ["a", "b", "c"]) let v = Variable(book) let chapters = v.map{ $0.chapters.map { $0.uppercased() } } // Uppercasing is there to remove updatability. XCTAssertEqual(chapters.isBuffered, false) XCTAssertEqual(chapters.count, 3) XCTAssertEqual(chapters.observableCount.value, 3) XCTAssertEqual(chapters.value, ["A", "B", "C"]) XCTAssertEqual(chapters[0], "A") XCTAssertEqual(chapters[1 ..< 3], ["B", "C"]) let mock = MockArrayObserver(chapters) mock.expecting(["begin", "3.insert(D, at: 3)", "end"]) { book.chapters.append("d") } XCTAssertEqual(chapters.value, ["A", "B", "C", "D"]) let book2 = Book("other", chapters: ["10", "11"]) mock.expecting(["begin", "4.replaceSlice([A, B, C, D], at: 0, with: [10, 11])", "end"]) { v.value = book2 } XCTAssertEqual(chapters.value, ["10", "11"]) } func test_updatableArrayField() { let book = Book("book", chapters: ["1", "2", "3"]) let v = Variable(book) let chapters = v.map{$0.chapters} XCTAssertEqual(chapters.isBuffered, true) XCTAssertEqual(chapters.count, 3) XCTAssertEqual(chapters.observableCount.value, 3) XCTAssertEqual(chapters.value, ["1", "2", "3"]) XCTAssertEqual(chapters[0], "1") XCTAssertEqual(chapters[1 ..< 3], ["2", "3"]) let mock = MockArrayObserver(chapters) mock.expecting(["begin", "3.insert(4, at: 3)", "end"]) { book.chapters.append("4") } XCTAssertEqual(chapters.value, ["1", "2", "3", "4"]) mock.expecting(["begin", "4.remove(3, at: 2)", "end"]) { _ = chapters.remove(at: 2) } XCTAssertEqual(chapters.value, ["1", "2", "4"]) XCTAssertEqual(book.chapters.value, ["1", "2", "4"]) let book2 = Book("other", chapters: ["10", "11"]) mock.expecting(["begin", "3.replaceSlice([1, 2, 4], at: 0, with: [10, 11])", "end"]) { v.value = book2 } XCTAssertEqual(chapters.value, ["10", "11"]) mock.expecting(["begin", "2.replace(10, at: 0, with: 20)", "end"]) { chapters[0] = "20" } XCTAssertEqual(chapters.value, ["20", "11"]) XCTAssertEqual(book2.chapters.value, ["20", "11"]) mock.expecting(["begin", "2.insert(25, at: 1)", "end"]) { chapters.insert("25", at: 1) } XCTAssertEqual(chapters.value, ["20", "25", "11"]) XCTAssertEqual(book2.chapters.value, ["20", "25", "11"]) mock.expecting(["begin", "3.replaceSlice([25, 11], at: 1, with: [21, 22])", "end"]) { chapters[1 ..< 3] = ["21", "22"] } XCTAssertEqual(chapters.value, ["20", "21", "22"]) XCTAssertEqual(book2.chapters.value, ["20", "21", "22"]) mock.expecting(["begin", "3.replaceSlice([20, 21, 22], at: 0, with: [foo, bar])", "end"]) { chapters.value = ["foo", "bar"] } XCTAssertEqual(chapters.value, ["foo", "bar"]) XCTAssertEqual(book2.chapters.value, ["foo", "bar"]) } func test_setField() { let book = Book("book", authors: ["a", "b", "c"]) let v = Variable(book) let authors = v.map { $0.authors.map { $0.uppercased() } } // Uppercased to lose updatability. XCTAssertEqual(authors.isBuffered, false) XCTAssertEqual(authors.count, 3) XCTAssertEqual(authors.observableCount.value, 3) XCTAssertEqual(authors.value, ["A", "B", "C"]) XCTAssertEqual(authors.contains("A"), true) XCTAssertEqual(authors.contains("0"), false) XCTAssertEqual(authors.isSubset(of: ["A", "B", "C"]), true) XCTAssertEqual(authors.isSubset(of: ["A", "B", "C", "D"]), true) XCTAssertEqual(authors.isSubset(of: ["B", "C", "D"]), false) XCTAssertEqual(authors.isSuperset(of: ["A", "B", "C"]), true) XCTAssertEqual(authors.isSuperset(of: ["B", "C"]), true) XCTAssertEqual(authors.isSuperset(of: ["C", "D"]), false) let mock = MockSetObserver(authors) mock.expecting(["begin", "[]/[D]", "end"]) { book.authors.insert("d") } XCTAssertEqual(authors.value, ["A", "B", "C", "D"]) mock.expecting(["begin", "[B]/[]", "end"]) { book.authors.remove("b") } XCTAssertEqual(authors.value, ["A", "C", "D"]) mock.expecting(["begin", "[A, C, D]/[BARNEY, FRED]", "end"]) { v.value = Book("other", authors: ["fred", "barney"]) } XCTAssertEqual(authors.value, ["FRED", "BARNEY"]) } func test_updatableSetField() { let book = Book("book", authors: ["a", "b", "c"]) let v = Variable(book) let authors = v.map{$0.authors} XCTAssertEqual(authors.isBuffered, true) XCTAssertEqual(authors.count, 3) XCTAssertEqual(authors.observableCount.value, 3) XCTAssertEqual(authors.value, ["a", "b", "c"]) XCTAssertEqual(authors.contains("a"), true) XCTAssertEqual(authors.contains("0"), false) XCTAssertEqual(authors.isSubset(of: ["a", "b", "c"]), true) XCTAssertEqual(authors.isSubset(of: ["a", "b", "c", "d"]), true) XCTAssertEqual(authors.isSubset(of: ["b", "c", "d"]), false) XCTAssertEqual(authors.isSuperset(of: ["a", "b", "c"]), true) XCTAssertEqual(authors.isSuperset(of: ["b", "c"]), true) XCTAssertEqual(authors.isSuperset(of: ["c", "d"]), false) let mock = MockSetObserver(authors) mock.expecting(["begin", "[]/[d]", "end"]) { book.authors.insert("d") } XCTAssertEqual(authors.value, ["a", "b", "c", "d"]) mock.expecting(["begin", "[b]/[]", "end"]) { book.authors.remove("b") } XCTAssertEqual(authors.value, ["a", "c", "d"]) mock.expecting(["begin", "[]/[e]", "end"]) { authors.insert("e") } XCTAssertEqual(authors.value, ["a", "c", "d", "e"]) XCTAssertEqual(book.authors.value, ["a", "c", "d", "e"]) mock.expecting(["begin", "[c]/[]", "end"]) { authors.remove("c") } XCTAssertEqual(authors.value, ["a", "d", "e"]) XCTAssertEqual(book.authors.value, ["a", "d", "e"]) mock.expecting(["begin", "[]/[b, c]", "end"]) { authors.apply(SetChange(removed: [], inserted: ["b", "c"])) } XCTAssertEqual(authors.value, ["a", "b", "c", "d", "e"]) XCTAssertEqual(book.authors.value, ["a", "b", "c", "d", "e"]) mock.expecting(["begin", "[a, b, c, d, e]/[bar, foo]", "end"]) { authors.value = ["foo", "bar"] } XCTAssertEqual(authors.value, ["foo", "bar"]) XCTAssertEqual(book.authors.value, ["foo", "bar"]) mock.expecting(["begin", "[bar, foo]/[barney, fred]", "end"]) { v.value = Book("other", authors: ["fred", "barney"]) } XCTAssertEqual(authors.value, ["fred", "barney"]) } } ================================================ FILE: Tests/GlueKitTests/ValueReferenceTests.swift ================================================ // // ValueReferenceTests.swift // GlueKit // // Created by Károly Lőrentey on 2016-11-02. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest import GlueKit class ValueReferenceTests: XCTestCase { func testReference() { let a = Variable(0) let b = Variable(10) let c = Variable(20) let ref = Variable>(a.anyObservableValue) XCTAssertEqual(ref.value.value, 0) a.value = 1 XCTAssertEqual(ref.value.value, 1) let unpacked = ref.unpacked() XCTAssertEqual(unpacked.value, 1) a.value = 2 XCTAssertEqual(unpacked.value, 2) let sink = MockValueUpdateSink(unpacked) sink.expecting(["begin", "2 -> 3", "end"]) { a.value = 3 } sink.expecting(["begin", "3 -> 10", "end"]) { ref.value = b.anyObservableValue } sink.expecting(["begin", "10 -> 11", "end"]) { b.value = 11 } sink.expecting("begin") { b.apply(.beginTransaction) } sink.expectingNothing { ref.apply(.beginTransaction) } sink.expecting("11 -> 12") { b.value = 12 } sink.expecting("12 -> 20") { ref.value = c.anyObservableValue } sink.expecting("20 -> 21") { c.value = 21 } sink.expectingNothing { b.apply(.endTransaction) } sink.expecting("end") { ref.apply(.endTransaction) } sink.disconnect() } func testDerivedObservable() { let a = Variable(0) let double = a.map { 2 * $0 } let ref = Variable>(a.anyObservableValue) let sink = MockValueUpdateSink(ref.unpacked()) sink.expecting(["begin", "0 -> 1", "end"]) { a.value = 1 } sink.expecting(["begin", "1 -> 2", "end"]) { ref.value = double.anyObservableValue } sink.expecting(["begin", "2 -> 4", "end"]) { a.value = 2 } sink.disconnect() } } ================================================ FILE: Tests/GlueKitTests/VariableTests.swift ================================================ // // VariableTests.swift // GlueKit // // Created by Károly Lőrentey on 2015-12-01. // Copyright © 2015–2017 Károly Lőrentey. // import XCTest @testable import GlueKit class VariableTests: XCTestCase { func test_values() { let v = Variable(0) var r = [Int]() let c = v.values.subscribe { value in r.append(value) } XCTAssertEqual(r, [0], "The values source should trigger immediately with the current value of the variable") v.value = 1 XCTAssertEqual(r, [0, 1]) v.value = 2 XCTAssertEqual(r, [0, 1, 2]) v.value = 2 XCTAssertEqual(r, [0, 1, 2, 2]) v.withTransaction { v.value = 3 v.value = 3 } XCTAssertEqual(r, [0, 1, 2, 2, 3]) v.apply(ValueChange(from: 3, to: 4)) XCTAssertEqual(r, [0, 1, 2, 2, 3, 4]) c.disconnect() } func test_futureValues() { let v = Variable(0) var r = [Int]() let c = v.futureValues.subscribe { value in r.append(value) } XCTAssertEqual(r, [], "The future values source should not trigger with the current value of the variable") v.value = 1 XCTAssertEqual(r, [1]) v.value = 2 XCTAssertEqual(r, [1, 2]) v.value = 2 XCTAssertEqual(r, [1, 2, 2]) v.withTransaction { v.value = 3 v.value = 3 } XCTAssertEqual(r, [1, 2, 2, 3]) v.apply(ValueChange(from: 3, to: 4)) XCTAssertEqual(r, [1, 2, 2, 3, 4]) c.disconnect() } func test_updates_NestedUpdates() { let v = Variable(3) var s = "" let c = v.updates.subscribe { update in s += " (\(describe(update))" if let new = update.change?.new, new > 0 { // This is OK as long as it doesn't lead to infinite updates. // The value is updated immediately, but the source is triggered later, at the end of the outermost update. v.value -= 1 } s += ")" } XCTAssertEqual(s, "") s = "" v.value = 2 XCTAssertEqual(s, " (begin) (3 -> 2) (2 -> 1) (1 -> 0) (end)") c.disconnect() } func test_values_NestedUpdates() { let v = Variable(3) var s = "" let c = v.values.subscribe { i in s += " (\(i)" if i > 0 { // This is OK as long as it doesn't lead to infinite updates. // The value is updated immediately, but the source is triggered later, at the end of the outermost update. v.value -= 1 } s += ")" } XCTAssertEqual(s, " (3) (2) (1) (0)") // No nesting, all updates are received s = "" v.value = 1 XCTAssertEqual(s, " (1) (0)") c.disconnect() } func test_futureValues_NestedUpdates() { let v = Variable(0) var s = "" let c = v.futureValues.subscribe { i in s += " (\(i)" if i > 0 { // This is OK as long as it doesn't lead to infinite updates. // The value is updated immediately, but the source is triggered later, at the end of the outermost update. v.value -= 1 } s += ")" } XCTAssertEqual(s, "") v.value = 3 XCTAssertEqual(s, " (3) (2) (1) (0)") // No nesting, all updates are received s = "" v.value = 1 XCTAssertEqual(s, " (1) (0)") c.disconnect() } func test_values_ReentrantSinks() { let v = Variable(0) var s = String() let c1 = v.values.subscribe { i in s += " (\(i)" if i > 0 { v.value = i - 1 } s += ")" } let c2 = v.values.subscribe { i in s += " (\(i)" if i > 0 { v.value = i - 1 } s += ")" } XCTAssertEqual(s, " (0) (0)") s = "" v.value = 2 XCTAssertEqual(s, " (2) (2) (1) (1) (1) (1) (0) (0) (0) (0) (0) (0) (0) (0)") c1.disconnect() c2.disconnect() } func testExerciseVariables() { func check(_ v: V, _ a: V.Value, _ b: V.Value, _ c: V.Value) where V.Value: Equatable { check(v, a, b, c, ==) } func check(_ v: V, _ a: V.Value, _ b: V.Value, _ c: V.Value, _ eq: @escaping (V.Value, V.Value) -> Bool) { XCTAssert(eq(v.value, a)) v.value = a XCTAssert(eq(v.value, a)) v.value = b XCTAssert(eq(v.value, b)) v.withTransaction { v.value = a v.value = c } XCTAssert(eq(v.value, c)) v.value = a let mock = MockValueUpdateSink(v) mock.expecting(["begin", "\(a) -> \(b)", "end"]) { v.value = b } XCTAssert(eq(v.value, b)) mock.expecting(["begin", "\(b) -> \(a)", "\(a) -> \(c)", "end"]) { v.withTransaction { v.value = a v.value = c } } XCTAssert(eq(v.value, c)) } check(Variable(1), 1, 2, 3) check(IntVariable(1), 1, 2, 3) check(FloatVariable(1.0), 1.0, 2.0, 3.0) check(BoolVariable(false), false, true, false) check(StringVariable("foo"), "foo", "bar", "baz") check(Variable(Box(1)), Box(1), Box(2), Box(3)) let box = Box(1) check(UnownedVariable(box), Box(1), Box(2), Box(3)) check(WeakVariable(box), Box(1), Box(2), Box(3), ==) } func testUnownedVariable() { weak var box: Box? = nil let variable: UnownedVariable do { let b = Box(1) box = b variable = .init(b) XCTAssertEqual(box, Box(1)) XCTAssertEqual(variable.value, Box(1)) withExtendedLifetime(b) {} } XCTAssertNil(box, "An UnownedVariable must not retain its value") _ = variable // Accessing its value would trap here } func testWeakVariable() { weak var box: Box? = nil let variable: WeakVariable do { let b = Box(1) box = b variable = .init(b) XCTAssertEqual(box, Box(1)) XCTAssertEqual(variable.value, Box(1)) withExtendedLifetime(b) {} } XCTAssertNil(box) XCTAssertNil(variable.value) let nilVariable = WeakVariable() XCTAssertNil(nilVariable.value) } func testLiteralExpressibility() { let int: IntVariable = 1 XCTAssertEqual(int.value, 1) let float: FloatVariable = 2.0 XCTAssertEqual(float.value, 2.0) let double: DoubleVariable = 2.0 XCTAssertEqual(double.value, 2.0) let bool: BoolVariable = true XCTAssertEqual(bool.value, true) let string1: StringVariable = "foo" XCTAssertEqual(string1.value, "foo") let string2 = StringVariable(unicodeScalarLiteral: "bar") // ¯\_(ツ)_/¯ XCTAssertEqual(string2.value, "bar") let string3 = StringVariable(extendedGraphemeClusterLiteral: "baz") // ¯\_(ツ)_/¯ XCTAssertEqual(string3.value, "baz") let optional: OptionalVariable = nil XCTAssertEqual(optional.value, nil) } } private class Box: Equatable { var value: Int init(_ value: Int) { self.value = value } static func ==(a: Box, b: Box) -> Bool { return a.value == b.value } } ================================================ FILE: Tests/PerformanceTests/GlueKitPerformanceTests.swift ================================================ // // GlueKitPerformanceTests.swift // GlueKitPerformanceTests // // Created by Károly Lőrentey on 2015-12-04. // Copyright © 2015–2017 Károly Lőrentey. // import Foundation import XCTest import GlueKit private struct EmptySink: SinkType { let id: Int func receive(_ value: Int) { // Do nothing } var hashValue: Int { return id } static func ==(left: EmptySink, right: EmptySink) -> Bool { return left.id == right.id } } private class TestSink: SinkType { var count = 0 func receive(_ value: Int) { count += 1 } } private struct RefCountingSink: SinkType { let object: TestSink let id: Int func receive(_ value: Int) { // Do nothing } var hashValue: Int { return id } static func ==(left: RefCountingSink, right: RefCountingSink) -> Bool { return left.id == right.id } } private struct TestMethodSink: SinkType { let object: TestSink let method: (TestSink) -> (Int) -> Void let id: Int func receive(_ value: Int) { method(object)(value) } var hashValue: Int { return ObjectIdentifier(object).hashValue ^ id } static func ==(left: TestMethodSink, right: TestMethodSink) -> Bool { return left.object === right.object && left.id == right.id } } private struct TestPartiallyAppliedMethodSink: SinkType { let object: TestSink let method: (Int) -> Void let id: Int func receive(_ value: Int) { method(value) } var hashValue: Int { return ObjectIdentifier(object).hashValue ^ id } static func ==(left: TestPartiallyAppliedMethodSink, right: TestPartiallyAppliedMethodSink) -> Bool { return left.object === right.object && left.id == right.id } } private struct HardwiredMethodSink: SinkType { let object: TestSink let id: Int func receive(_ value: Int) { object.receive(value) } var hashValue: Int { return ObjectIdentifier(object).hashValue ^ id } static func ==(left: HardwiredMethodSink, right: HardwiredMethodSink) -> Bool { return left.object === right.object && left.id == right.id } } extension XCTestCase { func measureDelayed(_ body: @escaping () -> ()) { self.measureMetrics(XCTestCase.defaultPerformanceMetrics, automaticallyStartMeasuring: false, for: body) } } class SignalSubscriptionTests: XCTestCase { func test_subscribe_EmptySink() { let count = 100_000 self.measureDelayed { let signal = Signal() self.startMeasuring() for i in 0 ..< count { signal.add(EmptySink(id: i)) } self.stopMeasuring() signal.send(1) for i in 0 ..< count { signal.remove(EmptySink(id: i)) } XCTAssertFalse(signal.isConnected) } } func test_subscribe_RefCountingSink() { let count = 100_000 self.measureDelayed { let signal = Signal() let object = TestSink() self.startMeasuring() for i in 0 ..< count { signal.add(RefCountingSink(object: object, id: i)) } self.stopMeasuring() signal.send(1) for i in 0 ..< count { signal.remove(RefCountingSink(object: object, id: i)) } XCTAssertFalse(signal.isConnected) } } func test_subscribe_MethodSink() { let count = 100_000 self.measureDelayed { let signal = Signal() let object = TestSink() self.startMeasuring() for i in 0 ..< count { signal.add(TestMethodSink(object: object, method: TestSink.receive, id: i)) } self.stopMeasuring() signal.send(1) for i in 0 ..< count { signal.remove(TestMethodSink(object: object, method: TestSink.receive, id: i)) } XCTAssertFalse(signal.isConnected) XCTAssertEqual(object.count, count) } } func test_subscribe_PartiallyAppliedMethodSink() { let count = 100_000 self.measureDelayed { let signal = Signal() let object = TestSink() self.startMeasuring() for i in 0 ..< count { signal.add(TestPartiallyAppliedMethodSink(object: object, method: object.receive, id: i)) } self.stopMeasuring() signal.send(1) for i in 0 ..< count { signal.remove(TestPartiallyAppliedMethodSink(object: object, method: object.receive, id: i)) } XCTAssertFalse(signal.isConnected) XCTAssertEqual(object.count, count) } } func test_subscribe_HardwiredMethodSink() { let count = 100_000 self.measureDelayed { let signal = Signal() let object = TestSink() self.startMeasuring() for i in 0 ..< count { signal.add(HardwiredMethodSink(object: object, id: i)) } self.stopMeasuring() signal.send(1) for i in 0 ..< count { signal.remove(HardwiredMethodSink(object: object, id: i)) } XCTAssertFalse(signal.isConnected) //XCTAssertEqual(object.count, count) } } func test_subscribe_Closures() { let count = 100_000 self.measureDelayed { let signal = Signal() var received = 0 var connections: [Connection] = [] self.startMeasuring() for _ in 0 ..< count { let c = signal.subscribe { _ in received += 1 } connections.append(c) } self.stopMeasuring() signal.send(1) for c in connections { c.disconnect() } XCTAssertFalse(signal.isConnected) XCTAssertEqual(received, count) } } } class SignalUnsubscriptionTests: XCTestCase { func test_unsubscribe_emptySinks() { let count = 100_000 self.measureDelayed { let signal = Signal() for i in 0 ..< count { signal.add(EmptySink(id: i)) } signal.send(1) self.startMeasuring() for i in 0 ..< count { signal.remove(EmptySink(id: i)) } self.stopMeasuring() XCTAssertFalse(signal.isConnected) } } } class SignalSendTests: XCTestCase { func test_send_toSink() { let iterations = 200_000 measureDelayed { let signal = Signal() let sink = TestSink() signal.add(sink) self.startMeasuring() for i in 1...iterations { signal.send(i) } self.stopMeasuring() signal.remove(sink) XCTAssertEqual(sink.count, iterations) } } func test_send_toClosure() { // Sending to a closure should take roughly the same time as sending to a sink. let iterations = 200_000 self.measureDelayed { var count = 0 let signal = Signal() let c = signal.subscribe { i in count += 1 } self.startMeasuring() for i in 1...iterations { signal.send(i) } self.stopMeasuring() c.disconnect() XCTAssertEqual(count, iterations) } } func testConcurrentSendPerformance() { let queueCount = 4 let iterations = 30000 self.measureDelayed { var count = 0 let signal = Signal() let c = signal.subscribe { i in count += 1 } let queues = (1...queueCount).map { i in DispatchQueue(label: "org.attaswift.GlueKit.testQueue \(i)") } let group = DispatchGroup() self.startMeasuring() for q in queues { q.async(group: group) { for i in 1...iterations { signal.send(i) } } } group.wait() self.stopMeasuring() c.disconnect() XCTAssertEqual(count, iterations * queueCount) } } func testChainedSendPerformance() { // Chain 1000 signals together, then send a bunch of numbers through the chain. var connections: [Connection] = [] let start = Signal() var end = start (1...1000).forEach { _ in let new = Signal() connections.append(end.subscribe(new.send)) end = new } let count = 100 self.measureDelayed { var r = [Int]() r.reserveCapacity(1000) let c = end.subscribe { i in r.append(i) } for i in 1...10 { start.send(i) } r.removeAll(keepingCapacity: true) self.startMeasuring() for i in 1...count { start.send(i) } self.stopMeasuring() c.disconnect() XCTAssertEqual(r.count, count) } } } ================================================ FILE: Tests/PerformanceTests/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleVersion 1 ================================================ FILE: jazzy.sh ================================================ #!/bin/sh jazzy \ --clean \ --author "Károly Lőrentey" \ --author_url "https://twitter.com/lorentey" \ --github_url https://github.com/lorentey/GlueKit \ --github-file-prefix https://github.com/lorentey/GlueKit/tree/master \ --module-version 1.0.0-alpha.1 \ --xcodebuild-arguments -scheme,GlueKit \ --module GlueKit \ --root-url https://lorentey.github.io/GlueKit/reference/ \ --output jazzy/output \ --swift-version 2.1.1 #--template-directory jazzy/templates \ ================================================ FILE: version.xcconfig ================================================ // // version.xcconfig // // Created by Károly Lőrentey on 2016-03-08. // Copyright © 2015–2017 Károly Lőrentey. // // Increment the build number whenever you modify the version string. VERSION_STRING = 0.2.0 BUILD_NUMBER = 2 PROJECT_NAME = GlueKit BUNDLE_IDENTIFIER_BASE = org.attaswift.$(PROJECT_NAME) IPHONEOS_DEPLOYMENT_TARGET = 9.3 MACOSX_DEPLOYMENT_TARGET = 10.11 WATCHOS_DEPLOYMENT_TARGET = 3.0 TVOS_DEPLOYMENT_TARGET = 10.0 APPLICATION_EXTENSION_API_ONLY = YES