├── .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
/.codecov.yml:
--------------------------------------------------------------------------------
1 | ignore:
2 | - "/Tests/*"
3 | comment:
4 | layout: "header, diff"
5 | behavior: default
6 | require_changes: no
7 | coverage:
8 | status:
9 | project:
10 | default:
11 | target: auto
12 | threshold: null
13 | base: auto
14 | paths: "Sources/*"
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .build
2 | /Carthage/Build
3 | /Packages
4 | xcuserdata
5 | /Package.resolved
6 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "Carthage/Checkouts/BTree"]
2 | path = Carthage/Checkouts/BTree
3 | url = https://github.com/attaswift/BTree.git
4 | [submodule "Carthage/Checkouts/SipHash"]
5 | path = Carthage/Checkouts/SipHash
6 | url = https://github.com/attaswift/SipHash.git
7 |
--------------------------------------------------------------------------------
/.swift-version:
--------------------------------------------------------------------------------
1 | 3.0.1
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: objective-c
2 | osx_image: xcode9
3 | script:
4 | - xcrun xcodebuild -workspace GlueKit.xcworkspace -scheme GlueKit-macOS test
5 | - xcrun xcodebuild -quiet -workspace GlueKit.xcworkspace -scheme GlueKit-iOS
6 | - xcrun xcodebuild -quiet -workspace GlueKit.xcworkspace -scheme GlueKit-watchOS
7 | - xcrun xcodebuild -quiet -workspace GlueKit.xcworkspace -scheme GlueKit-tvOS
8 | - swift test
9 | after_success: bash <(curl -s https://codecov.io/bash)
10 |
--------------------------------------------------------------------------------
/Cartfile:
--------------------------------------------------------------------------------
1 | github "attaswift/BTree" ~> 4.1
2 | github "attaswift/SipHash" ~> 1.2
3 |
--------------------------------------------------------------------------------
/Cartfile.resolved:
--------------------------------------------------------------------------------
1 | github "attaswift/BTree" "v4.1.0"
2 | github "attaswift/SipHash" "v1.2.0"
3 |
--------------------------------------------------------------------------------
/Demo.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/GlueKit.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |spec|
2 | spec.name = 'GlueKit'
3 | spec.version = '0.2.0'
4 | spec.ios.deployment_target = "9.3"
5 | spec.osx.deployment_target = "10.11"
6 | spec.tvos.deployment_target = "10.0"
7 | spec.watchos.deployment_target = "3.0"
8 | spec.summary = 'Type-safe observable values and collections in Swift'
9 | spec.author = 'Károly Lőrentey'
10 | spec.homepage = 'https://github.com/attaswift/GlueKit'
11 | spec.license = { :type => 'MIT', :file => 'LICENSE.md' }
12 | spec.source = { :git => 'https://github.com/attaswift/GlueKit.git', :tag => 'v' + String(spec.version) }
13 | spec.source_files = 'Sources/*.swift'
14 | spec.social_media_url = 'https://twitter.com/lorentey'
15 | #spec.documentation_url = 'http://lorentey.github.io/GlueKit/'
16 | spec.dependency 'BTree', '~> 4.1'
17 | spec.dependency 'SipHash', '~> 1.2'
18 | end
19 |
--------------------------------------------------------------------------------
/GlueKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/GlueKit.xcodeproj/project.xcworkspace/xcshareddata/GlueKit.xcscmblueprint:
--------------------------------------------------------------------------------
1 | {
2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "1C1227F33F1818028D5EE61FD15F57BEC36D44F4",
3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : {
4 |
5 | },
6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : {
7 | "05D6826B0162543BD57EFC4EE6354EB49FD59438" : 9223372036854775807,
8 | "1C1227F33F1818028D5EE61FD15F57BEC36D44F4" : 9223372036854775807
9 | },
10 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "7F20ECAA-BD48-4077-9606-C54D512EB5F5",
11 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
12 | "05D6826B0162543BD57EFC4EE6354EB49FD59438" : "BTree\/",
13 | "1C1227F33F1818028D5EE61FD15F57BEC36D44F4" : "GlueKit\/"
14 | },
15 | "DVTSourceControlWorkspaceBlueprintNameKey" : "GlueKit",
16 | "DVTSourceControlWorkspaceBlueprintVersion" : 204,
17 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "GlueKit.xcodeproj",
18 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [
19 | {
20 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/lorentey\/BTree.git",
21 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
22 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "05D6826B0162543BD57EFC4EE6354EB49FD59438"
23 | },
24 | {
25 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/lorentey\/GlueKit.git",
26 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
27 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "1C1227F33F1818028D5EE61FD15F57BEC36D44F4"
28 | }
29 | ]
30 | }
--------------------------------------------------------------------------------
/GlueKit.xcodeproj/xcshareddata/xcbaselines/BB351AFB1DB81E67005F083F.xcbaseline/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | runDestinationsByUUID
6 |
7 | EBF79DD4-EE66-4D33-B51C-EE37857B70A1
8 |
9 | localComputer
10 |
11 | busSpeedInMHz
12 | 100
13 | cpuCount
14 | 1
15 | cpuKind
16 | Intel Core i7
17 | cpuSpeedInMHz
18 | 2600
19 | logicalCPUCoresPerPackage
20 | 8
21 | modelCode
22 | MacBookPro10,1
23 | physicalCPUCoresPerPackage
24 | 4
25 | platformIdentifier
26 | com.apple.platform.macosx
27 |
28 | targetArchitecture
29 | x86_64
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/GlueKit.xcodeproj/xcshareddata/xcbaselines/BBB55BE11C8FD1C60050DDA9.xcbaseline/DD663408-0BA9-46F9-868F-F1570927CA52.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | classNames
6 |
7 | SignalPerformanceTests
8 |
9 | testChainedSendPerformance()
10 |
11 | com.apple.XCTPerformanceMetric_WallClockTime
12 |
13 | baselineAverage
14 | 0.53204
15 | baselineIntegrationDisplayName
16 | 2016-10-19 23:23:14
17 |
18 |
19 | testConcurrentSendPerformance()
20 |
21 | com.apple.XCTPerformanceMetric_WallClockTime
22 |
23 | baselineAverage
24 | 1.6244
25 | baselineIntegrationDisplayName
26 | 2016-10-19 23:23:14
27 |
28 |
29 | testSendPerformance()
30 |
31 | com.apple.XCTPerformanceMetric_WallClockTime
32 |
33 | baselineAverage
34 | 0.6672
35 | baselineIntegrationDisplayName
36 | 2016-10-19 23:23:14
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/GlueKit.xcodeproj/xcshareddata/xcbaselines/BBB55BE11C8FD1C60050DDA9.xcbaseline/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | runDestinationsByUUID
6 |
7 | DD663408-0BA9-46F9-868F-F1570927CA52
8 |
9 | localComputer
10 |
11 | busSpeedInMHz
12 | 100
13 | cpuCount
14 | 1
15 | cpuKind
16 | Intel Core i7
17 | cpuSpeedInMHz
18 | 2600
19 | logicalCPUCoresPerPackage
20 | 8
21 | modelCode
22 | MacBookPro10,1
23 | physicalCPUCoresPerPackage
24 | 4
25 | platformIdentifier
26 | com.apple.platform.macosx
27 |
28 | targetArchitecture
29 | x86_64
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/GlueKit.xcodeproj/xcshareddata/xcschemes/GlueKit-watchOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
33 |
34 |
35 |
36 |
47 |
48 |
54 |
55 |
56 |
57 |
58 |
59 |
65 |
66 |
72 |
73 |
74 |
75 |
77 |
78 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/GlueKit.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/GlueKit.xcworkspace/xcshareddata/GlueKit.xcscmblueprint:
--------------------------------------------------------------------------------
1 | {
2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "1C1227F33F1818028D5EE61FD15F57BEC36D44F4",
3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : {
4 |
5 | },
6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : {
7 | "05D6826B0162543BD57EFC4EE6354EB49FD59438" : 9223372036854775807,
8 | "FB48966E443D25259D3CA0BE6E56D2D7343B7F97" : 9223372036854775807,
9 | "1C1227F33F1818028D5EE61FD15F57BEC36D44F4" : 9223372036854775807
10 | },
11 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "46A01E16-332E-44AB-A9D5-63D415AD87CA",
12 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
13 | "05D6826B0162543BD57EFC4EE6354EB49FD59438" : "GlueKit\/Carthage\/Checkouts\/BTree\/",
14 | "FB48966E443D25259D3CA0BE6E56D2D7343B7F97" : "GlueKit\/Carthage\/Checkouts\/SipHash\/",
15 | "1C1227F33F1818028D5EE61FD15F57BEC36D44F4" : "GlueKit\/"
16 | },
17 | "DVTSourceControlWorkspaceBlueprintNameKey" : "GlueKit",
18 | "DVTSourceControlWorkspaceBlueprintVersion" : 204,
19 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "GlueKit.xcworkspace",
20 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [
21 | {
22 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/lorentey\/BTree.git",
23 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
24 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "05D6826B0162543BD57EFC4EE6354EB49FD59438"
25 | },
26 | {
27 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/lorentey\/GlueKit.git",
28 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
29 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "1C1227F33F1818028D5EE61FD15F57BEC36D44F4"
30 | },
31 | {
32 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/lorentey\/SipHash.git",
33 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
34 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "FB48966E443D25259D3CA0BE6E56D2D7343B7F97"
35 | }
36 | ]
37 | }
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015–2017 Károly Lőrentey
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:4.0
2 | import PackageDescription
3 |
4 | let package = Package(
5 | name: "GlueKit",
6 | products: [
7 | .library(name: "GlueKit", type: .dynamic, targets: ["GlueKit"])
8 | ],
9 | dependencies: [
10 | .package(url: "https://github.com/attaswift/SipHash", from: "1.2.0"),
11 | .package(url: "https://github.com/attaswift/BTree", from: "4.1.0")
12 | ],
13 | targets: [
14 | .target(name: "GlueKit", dependencies: ["BTree", "SipHash"], path: "Sources"),
15 | .testTarget(name: "GlueKitTests", dependencies: ["GlueKit"], path: "Tests/GlueKitTests"),
16 | .testTarget(name: "PerformanceTests", dependencies: ["GlueKit"], path: "Tests/PerformanceTests")
17 | ],
18 | swiftLanguageVersions: [4]
19 | )
20 |
--------------------------------------------------------------------------------
/Sources/Abstract.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Abstract.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2016-08-22.
6 | // Copyright © 2015–2017 Károly Lőrentey.
7 | //
8 |
9 | internal func abstract() -> Never {
10 | fatalError("Unimplemented abstract method")
11 | }
12 |
--------------------------------------------------------------------------------
/Sources/AccumulatedSource.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AccumulatedSource.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2017-04-23.
6 | // Copyright © 2015–2017 Károly Lőrentey.
7 | //
8 |
9 | extension SourceType {
10 | public func accumulated(_ initial: R, _ next: @escaping (R, Value) -> R) -> AnyObservableValue {
11 | return AccumulatedSource(self, initial, next).anyObservableValue
12 | }
13 |
14 | public func counted() -> AnyObservableValue {
15 | return accumulated(0) { value, _ in value + 1 }
16 | }
17 | }
18 |
19 | private class AccumulatedSource: _BaseObservableValue where S: SourceType {
20 | let source: S
21 | let next: (Value, S.Value) -> Value
22 | var _value: Value
23 |
24 | struct Sink: UniqueOwnedSink {
25 | typealias Owner = AccumulatedSource
26 | unowned(unsafe) let owner: Owner
27 | func receive(_ value: S.Value) {
28 | owner.beginTransaction()
29 | let old = owner._value
30 | let new = owner.next(owner._value, value)
31 | owner._value = new
32 | owner.sendChange(ValueChange(from: old, to: new))
33 | owner.endTransaction()
34 | }
35 | }
36 |
37 | init(_ source: S, _ initial: Value, _ next: @escaping (Value, S.Value) -> Value) {
38 | self.source = source
39 | self.next = next
40 | self._value = initial
41 | super.init()
42 | source.add(Sink(owner: self))
43 | }
44 |
45 | deinit {
46 | source.remove(Sink(owner: self))
47 | }
48 |
49 | override var value: Value { return _value }
50 | }
51 |
--------------------------------------------------------------------------------
/Sources/ArrayFilteringIndexmap.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ArrayFilteringIndexmap.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2016-10-07.
6 | // Copyright © 2015–2017 Károly Lőrentey.
7 | //
8 |
9 | import BTree
10 |
11 | internal struct ArrayFilteringIndexmap {
12 | let isIncluded: (Element) -> Bool
13 | var matchingIndices = SortedSet()
14 |
15 | init(initialValues values: [Element], isIncluded: @escaping (Element) -> Bool) {
16 | self.isIncluded = isIncluded
17 | for index in values.indices {
18 | if isIncluded(values[index]) {
19 | matchingIndices.insert(index)
20 | }
21 | }
22 | }
23 |
24 | mutating func apply(_ change: ArrayChange) -> ArrayChange {
25 | var filteredChange = ArrayChange(initialCount: matchingIndices.count)
26 | for mod in change.modifications {
27 | switch mod {
28 | case .insert(let element, at: let index):
29 | matchingIndices.shift(startingAt: index, by: 1)
30 | if isIncluded(element) {
31 | matchingIndices.insert(index)
32 | filteredChange.add(.insert(element, at: matchingIndices.offset(of: index)!))
33 | }
34 | case .remove(let element, at: let index):
35 | if let filteredIndex = matchingIndices.offset(of: index) {
36 | filteredChange.add(.remove(element, at: filteredIndex))
37 | }
38 | matchingIndices.shift(startingAt: index + 1, by: -1)
39 | case .replace(let old, at: let index, with: let new):
40 | switch (matchingIndices.offset(of: index), isIncluded(new)) {
41 | case (.some(let offset), true):
42 | filteredChange.add(.replace(old, at: offset, with: new))
43 | case (.none, true):
44 | matchingIndices.insert(index)
45 | filteredChange.add(.insert(new, at: matchingIndices.offset(of: index)!))
46 | case (.some(let offset), false):
47 | matchingIndices.remove(index)
48 | filteredChange.add(.remove(old, at: offset))
49 | case (.none, false):
50 | // Do nothing
51 | break
52 | }
53 | case .replaceSlice(let old, at: let index, with: let new):
54 | let filteredIndex = matchingIndices.prefix(upTo: index).count
55 | let filteredOld = matchingIndices.intersection(elementsIn: index ..< index + old.count).map { old[$0 - index] }
56 | var filteredNew: [Element] = []
57 |
58 | matchingIndices.subtract(elementsIn: index ..< index + old.count)
59 | matchingIndices.shift(startingAt: index + old.count, by: new.count - old.count)
60 | for i in 0 ..< new.count {
61 | if isIncluded(new[i]) {
62 | matchingIndices.insert(index + i)
63 | filteredNew.append(new[i])
64 | }
65 | }
66 | if let mod = ArrayModification(replacing: filteredOld, at: filteredIndex, with: filteredNew) {
67 | filteredChange.add(mod)
68 | }
69 | }
70 | }
71 | return filteredChange
72 | }
73 |
74 | mutating func insert(_ index: Int) -> Int? {
75 | guard !matchingIndices.contains(index) else { return nil }
76 | matchingIndices.insert(index)
77 | return matchingIndices.offset(of: index)!
78 | }
79 |
80 | mutating func remove(_ index: Int) -> Int? {
81 | guard let filteredIndex = matchingIndices.offset(of: index) else { return nil }
82 | matchingIndices.remove(index)
83 | return filteredIndex
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/Sources/ArrayFilteringOnPredicate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ArrayFilteringOnPredicate.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2016-09-26.
6 | // Copyright © 2015–2017 Károly Lőrentey.
7 | //
8 |
9 | extension ObservableArrayType {
10 | public func filter(_ isIncluded: @escaping (Element) -> Bool) -> AnyObservableArray {
11 | return ArrayFilteringOnPredicate(parent: self, isIncluded: isIncluded).anyObservableArray
12 | }
13 | }
14 |
15 | private final class ArrayFilteringOnPredicate: _BaseObservableArray {
16 | public typealias Element = Parent.Element
17 | public typealias Change = ArrayChange
18 |
19 | private struct ParentSink: UniqueOwnedSink {
20 | typealias Owner = ArrayFilteringOnPredicate
21 |
22 | unowned(unsafe) let owner: Owner
23 |
24 | func receive(_ update: ArrayUpdate) {
25 | owner.applyParentUpdate(update)
26 | }
27 | }
28 |
29 | private let parent: Parent
30 | private let isIncluded: (Element) -> Bool
31 |
32 | private var indexMapping: ArrayFilteringIndexmap
33 |
34 | init(parent: Parent, isIncluded: @escaping (Element) -> Bool) {
35 | self.parent = parent
36 | self.isIncluded = isIncluded
37 | self.indexMapping = ArrayFilteringIndexmap(initialValues: parent.value, isIncluded: isIncluded)
38 | super.init()
39 | parent.add(ParentSink(owner: self))
40 | }
41 |
42 | deinit {
43 | parent.remove(ParentSink(owner: self))
44 | }
45 |
46 | func applyParentUpdate(_ update: ArrayUpdate) {
47 | switch update {
48 | case .beginTransaction:
49 | beginTransaction()
50 | case .change(let change):
51 | let filteredChange = self.indexMapping.apply(change)
52 | if !filteredChange.isEmpty {
53 | sendChange(filteredChange)
54 | }
55 | case .endTransaction:
56 | endTransaction()
57 | }
58 | }
59 |
60 | override var isBuffered: Bool {
61 | return false
62 | }
63 |
64 | override subscript(index: Int) -> Element {
65 | return parent[indexMapping.matchingIndices[index]]
66 | }
67 |
68 | override subscript(bounds: Range) -> ArraySlice {
69 | precondition(0 <= bounds.lowerBound && bounds.lowerBound <= bounds.upperBound && bounds.upperBound <= count)
70 | var result: [Element] = []
71 | result.reserveCapacity(bounds.count)
72 | for index in indexMapping.matchingIndices[bounds] {
73 | result.append(parent[index])
74 | }
75 | return ArraySlice(result)
76 | }
77 |
78 | override var value: Array {
79 | return indexMapping.matchingIndices.map { parent[$0] }
80 | }
81 |
82 | override var count: Int {
83 | return indexMapping.matchingIndices.count
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/Sources/ArrayFolding.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ArrayFolding.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2016-10-09.
6 | // Copyright © 2015–2017 Károly Lőrentey.
7 | //
8 |
9 | extension ObservableArrayType {
10 | /// Returns an observable whose value is always equal to `self.value.reduce(initial, add)`.
11 | ///
12 | /// - Parameter initial: The accumulation starts with this initial value.
13 | /// - Parameter add: A closure that adds an element of the array into an accumulated value.
14 | /// - Parameter remove: A closure that cancels the effect of an earlier `add`.
15 | /// - Returns: An observable value for the reduction of this array.
16 | ///
17 | /// - Note: Elements are added and removed in no particular order.
18 | /// (I.e., the underlying binary operation over `Result` must form an abelian group.)
19 | ///
20 | /// - SeeAlso: `sum()` which returns a reduction using addition.
21 | public func reduce(_ initial: Result, add: @escaping (Result, Element) -> Result, remove: @escaping (Result, Element) -> Result) -> AnyObservableValue {
22 | return ArrayFoldingByTwoWayFunction(base: self, initial: initial, add: add, remove: remove).anyObservableValue
23 | }
24 | }
25 |
26 | extension ObservableArrayType where Element: BinaryInteger {
27 | /// Return the (observable) sum of the elements contained in this array.
28 | public func sum() -> AnyObservableValue {
29 | return reduce(0, add: +, remove: -)
30 | }
31 | }
32 |
33 | private class ArrayFoldingByTwoWayFunction: _BaseObservableValue {
34 | private var _value: Value
35 |
36 | let add: (Value, Base.Element) -> Value
37 | let remove: (Value, Base.Element) -> Value
38 | var connection: Connection? = nil
39 |
40 | init(base: Base, initial: Value, add: @escaping (Value, Base.Element) -> Value, remove: @escaping (Value, Base.Element) -> Value) {
41 | self._value = base.value.reduce(initial, add)
42 | self.add = add
43 | self.remove = remove
44 | super.init()
45 |
46 | connection = base.updates.subscribe { [unowned self] in self.apply($0) }
47 | }
48 |
49 | deinit {
50 | connection!.disconnect()
51 | }
52 |
53 | private func apply(_ update: ArrayUpdate) {
54 | switch update {
55 | case .beginTransaction:
56 | beginTransaction()
57 | case .change(let change):
58 | let old = _value
59 | change.forEachOld { _value = remove(_value, $0) }
60 | change.forEachNew { _value = add(_value, $0) }
61 | sendChange(ValueChange(from: old, to: _value))
62 | case .endTransaction:
63 | endTransaction()
64 | }
65 | }
66 |
67 | override var value: Value { return _value }
68 | }
69 |
--------------------------------------------------------------------------------
/Sources/ArrayGatheringSource.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ArrayGatheringSource.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2017-09-05.
6 | // Copyright © 2017 Károly Lőrentey. All rights reserved.
7 | //
8 |
9 | extension ObservableArrayType where Element: SourceType {
10 | public func gather() -> AnySource {
11 | return ArrayGatheringSource(self).anySource
12 | }
13 | }
14 |
15 | private class ArrayGatheringSource: _AbstractSource
16 | where Origin.Element: SourceType, Origin.Element.Value == Value {
17 | let origin: Origin
18 | var sinks: Set> = []
19 |
20 | private struct GatherSink: UniqueOwnedSink {
21 | typealias Owner = ArrayGatheringSource
22 | unowned let owner: Owner
23 |
24 | func receive(_ value: ArrayUpdate) {
25 | guard case let .change(change) = value else { return }
26 | change.forEachOld { source in
27 | for sink in owner.sinks {
28 | source.remove(sink)
29 | }
30 | }
31 | change.forEachNew { source in
32 | for sink in owner.sinks {
33 | source.add(sink)
34 | }
35 | }
36 | }
37 | }
38 |
39 | init(_ origin: Origin) {
40 | self.origin = origin
41 | }
42 |
43 | override func add(_ sink: Sink) where Sink.Value == Value {
44 | if sinks.isEmpty {
45 | origin.add(GatherSink(owner: self))
46 | }
47 | let new = sinks.insert(sink.anySink).inserted
48 | precondition(new)
49 | for source in origin.value {
50 | source.add(sink)
51 | }
52 | }
53 |
54 | @discardableResult
55 | override func remove(_ sink: Sink) -> Sink where Sink.Value == Value {
56 | let result = sinks.remove(sink.anySink)!
57 | for source in origin.value {
58 | source.remove(result)
59 | }
60 | if sinks.isEmpty {
61 | origin.remove(GatherSink(owner: self))
62 | }
63 | return result.opened()!
64 | }
65 | }
66 |
67 |
--------------------------------------------------------------------------------
/Sources/ArrayReference.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ArrayReference.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2016-08-17.
6 | // Copyright © 2015–2017 Károly Lőrentey.
7 | //
8 |
9 | extension ObservableValueType where Value: ObservableArrayType{
10 | public func unpacked() -> AnyObservableArray {
11 | return UnpackedObservableArrayReference(self).anyObservableArray
12 | }
13 | }
14 |
15 | /// A mutable reference to an `AnyObservableArray` that's also an observable array.
16 | /// You can switch to another target array without having to re-register subscribers.
17 | private final class UnpackedObservableArrayReference: _BaseObservableArray
18 | where Reference.Value: ObservableArrayType {
19 | typealias Target = Reference.Value
20 | typealias Element = Target.Element
21 | typealias Change = ArrayChange
22 |
23 | private struct ReferenceSink: UniqueOwnedSink {
24 | typealias Owner = UnpackedObservableArrayReference
25 |
26 | unowned(unsafe) let owner: Owner
27 |
28 | func receive(_ update: ValueUpdate) {
29 | owner.applyReferenceUpdate(update)
30 | }
31 | }
32 |
33 | private struct TargetSink: UniqueOwnedSink {
34 | typealias Owner = UnpackedObservableArrayReference
35 |
36 | unowned(unsafe) let owner: Owner
37 |
38 | func receive(_ update: ArrayUpdate) {
39 | owner.applyTargetUpdate(update)
40 | }
41 | }
42 |
43 | private var _reference: Reference
44 | private var _target: Reference.Value? = nil // Retained to make sure we keep it alive
45 |
46 | init(_ reference: Reference) {
47 | _reference = reference
48 | super.init()
49 | }
50 |
51 | override func activate() {
52 | _reference.updates.add(ReferenceSink(owner: self))
53 | let target = _reference.value
54 | _target = target
55 | target.updates.add(TargetSink(owner: self))
56 | }
57 |
58 | override func deactivate() {
59 | _target!.updates.remove(TargetSink(owner: self))
60 | _reference.updates.remove(ReferenceSink(owner: self))
61 | }
62 |
63 | func applyReferenceUpdate(_ update: ValueUpdate) {
64 | switch update {
65 | case .beginTransaction:
66 | beginTransaction()
67 | case .change(let change):
68 | if isConnected {
69 | _target!.remove(TargetSink(owner: self))
70 | _target = change.new
71 | _target!.add(TargetSink(owner: self))
72 | sendChange(ArrayChange(from: change.old.value, to: change.new.value))
73 | }
74 | case .endTransaction:
75 | endTransaction()
76 | }
77 | }
78 |
79 | func applyTargetUpdate(_ update: ArrayUpdate) {
80 | switch update {
81 | case .beginTransaction:
82 | beginTransaction()
83 | case .change(let change):
84 | sendChange(change)
85 | case .endTransaction:
86 | endTransaction()
87 | }
88 | }
89 |
90 | override var isBuffered: Bool { return false }
91 | override subscript(_ index: Int) -> Element { return _reference.value[index] }
92 | override subscript(_ range: Range) -> ArraySlice { return _reference.value[range] }
93 | override var value: [Element] { return _reference.value.value }
94 | override var count: Int { return _reference.value.count }
95 | }
96 |
--------------------------------------------------------------------------------
/Sources/ArrayVariable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ArrayVariable.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2015-12-08.
6 | // Copyright © 2015–2017 Károly Lőrentey.
7 | //
8 |
9 | open class ArrayVariable: _BaseUpdatableArray, ExpressibleByArrayLiteral {
10 | public typealias Value = Array
11 | public typealias Change = ArrayChange
12 |
13 | fileprivate var _value: [Element]
14 |
15 | public override init() {
16 | _value = []
17 | }
18 | public init(_ elements: [Element]) {
19 | _value = elements
20 | }
21 | public init(_ elements: S) where S.Iterator.Element == Element {
22 | _value = Array(elements)
23 | }
24 | public init(elements: Element...) {
25 | _value = elements
26 | }
27 | public required convenience init(arrayLiteral elements: Element...) {
28 | self.init(elements)
29 | }
30 |
31 | override func rawApply(_ change: ArrayChange) {
32 | _value.apply(change)
33 | }
34 |
35 | final public override var value: [Element] {
36 | get {
37 | return _value
38 | }
39 | set {
40 | if isConnected {
41 | let old = _value
42 | beginTransaction()
43 | _value = newValue
44 | sendChange(ArrayChange(from: old, to: newValue))
45 | endTransaction()
46 | }
47 | else {
48 | _value = newValue
49 | }
50 | }
51 | }
52 |
53 | final public override var count: Int {
54 | return _value.count
55 | }
56 |
57 | final public override var isBuffered: Bool {
58 | return true
59 | }
60 |
61 | final public override subscript(index: Int) -> Element {
62 | get {
63 | return _value[index]
64 | }
65 | set {
66 | if isConnected {
67 | let old = _value[index]
68 | beginTransaction()
69 | _value[index] = newValue
70 | sendChange(ArrayChange(initialCount: _value.count, modification: .replace(old, at: index, with: newValue)))
71 | endTransaction()
72 | }
73 | else {
74 | _value[index] = newValue
75 | }
76 | }
77 | }
78 |
79 | final public override subscript(bounds: Range) -> ArraySlice {
80 | get {
81 | return value[bounds]
82 | }
83 | set {
84 | if isConnected {
85 | let oldCount = _value.count
86 | let old = Array(_value[bounds])
87 | beginTransaction()
88 | _value[bounds] = newValue
89 | sendChange(ArrayChange(initialCount: oldCount,
90 | modification: .replaceSlice(old, at: bounds.lowerBound, with: Array(newValue))))
91 | endTransaction()
92 | }
93 | else {
94 | _value[bounds] = newValue
95 | }
96 | }
97 | }
98 | }
99 |
100 |
--------------------------------------------------------------------------------
/Sources/BracketingSource.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BracketingSource.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2016-10-25.
6 | // Copyright © 2015–2017 Károly Lőrentey.
7 | //
8 |
9 | extension SourceType {
10 | /// Returns a version of this source that optionally prefixes or suffixes all observers' received values
11 | /// with computed start/end values.
12 | ///
13 | /// For each new subscriber, the returned source evaluates `hello`; if it returns a non-nil value,
14 | /// the value is sent to the sink; then the sink is added to `self`.
15 | ///
16 | /// For each subscriber that is to be removed, the returned source first removes it from `self`, then
17 | /// evaluates `goodbye`; if it returns a non-nil value, the bracketing source sends it to the sink.
18 | public func bracketed(hello: @escaping () -> Value?, goodbye: @escaping () -> Value?) -> AnySource {
19 | return BracketingSource(self, hello: hello, goodbye: goodbye).anySource
20 | }
21 | }
22 |
23 | private class BracketingSink: SinkType {
24 | typealias Value = Sink.Value
25 |
26 | let sink: Sink
27 | var pendingValues: [Value]?
28 | var removed = false
29 |
30 | init(_ sink: Sink) {
31 | self.sink = sink
32 | self.pendingValues = nil
33 | }
34 |
35 | init(_ sink: Sink, _ initial: Value) {
36 | self.sink = sink
37 | self.pendingValues = [initial]
38 | }
39 |
40 | func receive(_ value: Value) {
41 | if pendingValues == nil {
42 | sink.receive(value)
43 | }
44 | else {
45 | pendingValues!.append(value)
46 | }
47 | }
48 |
49 | var hashValue: Int { return sink.hashValue }
50 | static func ==(left: BracketingSink, right: BracketingSink) -> Bool {
51 | return left.sink == right.sink
52 | }
53 | }
54 |
55 | private final class BracketingSource: _AbstractSource {
56 | typealias Value = Input.Value
57 | let input: Input
58 | let hello: () -> Value?
59 | let goodbye: () -> Value?
60 |
61 | init(_ input: Input, hello: @escaping () -> Value?, goodbye: @escaping () -> Value?) {
62 | self.input = input
63 | self.hello = hello
64 | self.goodbye = goodbye
65 | }
66 |
67 | final override func add(_ sink: Sink) where Sink.Value == Value {
68 | if let greeting = hello() {
69 | let bracketing = BracketingSink(sink, greeting)
70 | input.add(bracketing)
71 | while !bracketing.pendingValues!.isEmpty && !bracketing.removed {
72 | let value = bracketing.pendingValues!.removeFirst()
73 | bracketing.sink.receive(value)
74 | }
75 | bracketing.pendingValues = nil
76 | }
77 | else {
78 | input.add(BracketingSink(sink))
79 | }
80 | }
81 |
82 | @discardableResult
83 | final override func remove(_ sink: Sink) -> Sink where Sink.Value == Value {
84 | let old = input.remove(BracketingSink(sink))
85 | old.removed = true
86 | if let farewell = goodbye() {
87 | old.sink.receive(farewell)
88 | }
89 | return old.sink
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/Sources/BufferedArray.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BufferedArray.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2016-08-22.
6 | // Copyright © 2015–2017 Károly Lőrentey.
7 | //
8 |
9 | extension ObservableArrayType {
10 | public func buffered() -> AnyObservableArray {
11 | if isBuffered {
12 | return anyObservableArray
13 | }
14 | return BufferedObservableArray(self).anyObservableArray
15 | }
16 | }
17 |
18 | internal class BufferedObservableArray: _BaseObservableArray {
19 | typealias Element = Content.Element
20 | typealias Change = ArrayChange
21 |
22 | private struct BufferedSink: UniqueOwnedSink {
23 | typealias Owner = BufferedObservableArray
24 |
25 | unowned(unsafe) let owner: Owner
26 |
27 | func receive(_ update: ArrayUpdate) {
28 | owner.applyUpdate(update)
29 | }
30 | }
31 |
32 | private let _content: Content
33 | private var _value: [Element]
34 | private var _pendingChange: Change? = nil
35 |
36 | init(_ content: Content) {
37 | _content = content
38 | _value = content.value
39 | super.init()
40 | _content.add(BufferedSink(owner: self))
41 | }
42 |
43 | deinit {
44 | _content.remove(BufferedSink(owner: self))
45 | }
46 |
47 | func applyUpdate(_ update: ArrayUpdate) {
48 | switch update {
49 | case .beginTransaction:
50 | beginTransaction()
51 | case .change(let change):
52 | if _pendingChange != nil {
53 | _pendingChange!.merge(with: change)
54 | }
55 | else {
56 | _pendingChange = change
57 | }
58 | case .endTransaction:
59 | if let change = _pendingChange {
60 | _value.apply(change)
61 | _pendingChange = nil
62 | sendChange(change)
63 | }
64 | endTransaction()
65 | }
66 | }
67 |
68 | override var isBuffered: Bool {
69 | return true
70 | }
71 |
72 | override subscript(_ index: Int) -> Content.Element {
73 | return _value[index]
74 | }
75 |
76 | override subscript(_ range: Range) -> ArraySlice {
77 | return _value[range]
78 | }
79 |
80 | override var value: [Element] {
81 | return _value
82 | }
83 |
84 | override var count: Int {
85 | return _value.count
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/Sources/BufferedSet.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BufferedSet.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2016-11-02.
6 | // Copyright © 2015–2017 Károly Lőrentey.
7 | //
8 |
9 | extension ObservableSetType {
10 | public func buffered() -> AnyObservableSet {
11 | if isBuffered {
12 | return anyObservableSet
13 | }
14 | return BufferedObservableSet(self).anyObservableSet
15 | }
16 | }
17 |
18 | internal class BufferedObservableSet: _BaseObservableSet {
19 | typealias Element = Content.Element
20 | typealias Change = SetChange
21 |
22 | private struct BufferedSink: UniqueOwnedSink {
23 | typealias Owner = BufferedObservableSet
24 |
25 | unowned(unsafe) let owner: Owner
26 |
27 | func receive(_ update: SetUpdate) {
28 | owner.applyUpdate(update)
29 | }
30 | }
31 |
32 | private let _content: Content
33 | private var _value: Set
34 | private var _pendingChange: Change? = nil
35 |
36 | init(_ content: Content) {
37 | _content = content
38 | _value = content.value
39 | super.init()
40 | _content.add(BufferedSink(owner: self))
41 | }
42 |
43 | deinit {
44 | _content.remove(BufferedSink(owner: self))
45 | }
46 |
47 | func applyUpdate(_ update: SetUpdate) {
48 | switch update {
49 | case .beginTransaction:
50 | beginTransaction()
51 | case .change(let change):
52 | if _pendingChange != nil {
53 | _pendingChange!.merge(with: change)
54 | }
55 | else {
56 | _pendingChange = change
57 | }
58 | case .endTransaction:
59 | if let change = _pendingChange {
60 | _value.apply(change)
61 | _pendingChange = nil
62 | sendChange(change)
63 | }
64 | endTransaction()
65 | }
66 | }
67 |
68 | override var isBuffered: Bool {
69 | return true
70 | }
71 |
72 | override var count: Int {
73 | return _value.count
74 | }
75 |
76 | override var value: Set {
77 | return _value
78 | }
79 |
80 | override func contains(_ member: Element) -> Bool {
81 | return _value.contains(member)
82 | }
83 |
84 | override func isSubset(of other: Set) -> Bool {
85 | return _value.isSubset(of: other)
86 | }
87 |
88 | override func isSuperset(of other: Set) -> Bool {
89 | return _value.isSuperset(of: other)
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/Sources/BufferedSource.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BufferedSource.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2016-10-25.
6 | // Copyright © 2015–2017 Károly Lőrentey.
7 | //
8 |
9 | extension SourceType {
10 | public func buffered() -> AnySource {
11 | return BufferedSource(self).anySource
12 | }
13 | }
14 |
15 | private final class BufferedSource: SignalerSource, SinkType {
16 | typealias Value = Input.Value
17 |
18 | private let source: Input
19 |
20 | init(_ source: Input) {
21 | self.source = source
22 | super.init()
23 | }
24 |
25 | override func activate() {
26 | source.add(self.unowned())
27 | }
28 |
29 | override func deactivate() {
30 | source.remove(self.unowned())
31 | }
32 |
33 | func receive(_ value: Input.Value) {
34 | signal.send(value)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Sources/BufferedValue.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Observable Transformations.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2015-12-07.
6 | // Copyright © 2015–2017 Károly Lőrentey.
7 | //
8 |
9 | extension ObservableValueType {
10 | public func buffered() -> AnyObservableValue {
11 | return BufferedObservableValue(self).anyObservableValue
12 | }
13 | }
14 |
15 | private struct BufferedObservableSink: UniqueOwnedSink {
16 | typealias Owner = BufferedObservableValue
17 |
18 | unowned(unsafe) let owner: Owner
19 |
20 | func receive(_ update: ValueUpdate) {
21 | owner.apply(update)
22 | }
23 | }
24 |
25 | private class BufferedObservableValue: _BaseObservableValue {
26 | typealias Value = Base.Value
27 |
28 | private var _base: Base
29 |
30 | private var _value: Value
31 | private var _pending: Value? = nil
32 |
33 | init(_ base: Base) {
34 | self._base = base
35 | self._value = base.value
36 | super.init()
37 |
38 | _base.updates.add(BufferedObservableSink(owner: self))
39 | }
40 |
41 | deinit {
42 | _base.updates.remove(BufferedObservableSink(owner: self))
43 | }
44 |
45 | func apply(_ update: ValueUpdate) {
46 | switch update {
47 | case .beginTransaction:
48 | beginTransaction()
49 | case .change(let change):
50 | _pending = change.new
51 | case .endTransaction:
52 | if let pending = _pending {
53 | let old = _value
54 | _value = pending
55 | _pending = nil
56 | sendChange(.init(from: old, to: _value))
57 | }
58 | endTransaction()
59 | }
60 | }
61 |
62 | override var value: Value { return _value }
63 | }
64 |
--------------------------------------------------------------------------------
/Sources/CADisplayLink Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CADisplayLink Extensions.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2016-03-17.
6 | // Copyright © 2015–2017 Károly Lőrentey.
7 | //
8 |
9 | #if os(iOS)
10 | import QuartzCore
11 |
12 | private var associatedTargetKey: UInt8 = 0
13 |
14 | public class CADisplayLinkSource: SignalerSource {
15 | public typealias Value = CADisplayLink
16 |
17 | private var runLoop: RunLoop? = nil
18 | public var displayLink: CADisplayLink? = nil
19 |
20 | public override init() {
21 | super.init()
22 | }
23 |
24 | override func activate() {
25 | displayLink = CADisplayLink(target: self, selector: #selector(CADisplayLinkSource.tick(_:)))
26 | precondition(self.runLoop == nil)
27 | let runLoop = RunLoop.current
28 | self.runLoop = runLoop
29 | displayLink!.add(to: runLoop, forMode: RunLoopMode.commonModes)
30 | }
31 |
32 | override func deactivate() {
33 | precondition(runLoop != nil)
34 | displayLink!.remove(from: runLoop!, forMode: RunLoopMode.commonModes)
35 | displayLink = nil
36 | runLoop = nil
37 | }
38 |
39 | @objc private func tick(_ displayLink: CADisplayLink) {
40 | signal.send(displayLink)
41 | }
42 | }
43 | #endif
44 |
--------------------------------------------------------------------------------
/Sources/Change.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Change.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2016-08-12.
6 | // Copyright © 2015–2017 Károly Lőrentey.
7 | //
8 |
9 | /// Describes a change to an observable value.
10 | /// An instance of a type implementing this protocol contains just enough information to describe the difference
11 | /// between the old value and the new value of the observable.
12 | public protocol ChangeType {
13 | associatedtype Value
14 |
15 | /// Creates a new change description for a change that goes from `oldValue` to `newValue`.
16 | init(from oldValue: Value, to newValue: Value)
17 |
18 | /// Returns true if this change did not actually change the value of the observable.
19 | /// Noop changes aren't usually sent by observables, but it is possible to get them by merging a sequence of
20 | /// changes to a collection.
21 | var isEmpty: Bool { get }
22 |
23 | /// Applies this change on `value` in place.
24 | /// Note that not all changes may be applicable on all values.
25 | func apply(on value: inout Value)
26 |
27 | /// Applies this change on `value` and returns the result.
28 | /// Note that not all changes may be applicable on all values.
29 | func applied(on value: Value) -> Value
30 |
31 | /// Merge this change with the `next` change. The result is a single change description that describes the
32 | /// change of performing `self` followed by `next`.
33 | ///
34 | /// The resulting instance may take a shortcut when producing the result value if some information in `self`
35 | /// is overwritten by `next`.
36 | func merged(with next: Self) -> Self
37 |
38 | mutating func merge(with next: Self)
39 |
40 | /// Reverse the direction of this change, i.e., return a change that undoes the effect of this change.
41 | func reversed() -> Self
42 | }
43 |
44 |
45 | extension ChangeType {
46 | /// Applies this change on `value` and returns the result.
47 | /// Note that not all changes may be applicable on all values.
48 | public func applied(on value: Value) -> Value {
49 | var result = value
50 | self.apply(on: &result)
51 | return result
52 | }
53 |
54 | public func merged(with next: Self) -> Self {
55 | var temp = self
56 | temp.merge(with: next)
57 | return temp
58 | }
59 | }
60 |
61 |
--------------------------------------------------------------------------------
/Sources/ChangesSource.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChangesSource.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2016-10-22.
6 | // Copyright © 2015–2017 Károly Lőrentey.
7 | //
8 |
9 | extension ObservableType {
10 | /// A source that reports changes to the value of this observable.
11 | /// Changes reported correspond to complete transactions in `self.updates`.
12 | public var changes: AnySource {
13 | return ChangesSource(self).anySource
14 | }
15 | }
16 |
17 | private class ChangesSinkState {
18 | typealias Value = Update
19 |
20 | var pending: Change? = nil
21 |
22 | func apply(_ update: Update) -> Change? {
23 | switch update {
24 | case .beginTransaction:
25 | precondition(pending == nil)
26 | case .change(let change):
27 | if pending == nil {
28 | pending = change
29 | }
30 | else {
31 | pending!.merge(with: change)
32 | }
33 | case .endTransaction:
34 | if let change = pending {
35 | pending = nil
36 | if !change.isEmpty {
37 | return change
38 | }
39 | }
40 | }
41 | return nil
42 | }
43 | }
44 |
45 | private struct ChangesSink: SinkType where Wrapped.Value: ChangeType {
46 | typealias Change = Wrapped.Value
47 | typealias Value = Update
48 |
49 | let wrapped: Wrapped
50 | let state: ChangesSinkState?
51 |
52 | init(_ wrapped: Wrapped, withState needState: Bool) {
53 | self.wrapped = wrapped
54 | self.state = needState ? ChangesSinkState() : nil
55 | }
56 |
57 | func receive(_ update: Update) {
58 | if let change = state?.apply(update) {
59 | wrapped.receive(change)
60 | }
61 | }
62 |
63 | var hashValue: Int {
64 | return wrapped.hashValue
65 | }
66 |
67 | static func ==(left: ChangesSink, right: ChangesSink) -> Bool {
68 | return left.wrapped == right.wrapped
69 | }
70 | }
71 |
72 | internal class ChangesSource: _AbstractSource {
73 | typealias Change = Observable.Change
74 |
75 | let observable: Observable
76 |
77 | init(_ observable: Observable) {
78 | self.observable = observable
79 | }
80 |
81 | override func add(_ sink: Sink) where Sink.Value == Change {
82 | observable.add(ChangesSink(sink, withState: true))
83 | }
84 |
85 | @discardableResult
86 | override func remove(_ sink: Sink) -> Sink where Sink.Value == Change {
87 | let old = observable.remove(ChangesSink(sink, withState: false))
88 | return old.wrapped
89 | }
90 | }
91 |
92 | extension Connector {
93 | @discardableResult
94 | public func subscribe(_ observable: Observable, to sink: @escaping (Observable.Change) -> Void) -> Connection {
95 | return observable.changes.subscribe(sink).putInto(self)
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/Sources/ComputedUpdatable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ComputedUpdatable.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2016-11-11.
6 | // Copyright © 2015–2017 Károly Lőrentey.
7 | //
8 |
9 | public final class ComputedUpdatable: _BaseUpdatableValue {
10 | public let getter: () -> Value
11 | public let setter: (Value) -> ()
12 | public let refreshSource: AnySource?
13 |
14 | private var _value: Value
15 |
16 | private struct Sink: UniqueOwnedSink {
17 | typealias Owner = ComputedUpdatable
18 | unowned(unsafe) let owner: Owner
19 | func receive(_ value: Void) {
20 | owner.refresh()
21 | }
22 | }
23 |
24 | public init(getter: @escaping () -> Value,
25 | setter: @escaping (Value) -> (),
26 | refreshSource: AnySource? = nil) {
27 | self.getter = getter
28 | self.setter = setter
29 | self.refreshSource = refreshSource
30 | self._value = getter()
31 | super.init()
32 | refreshSource?.add(Sink(owner: self))
33 | }
34 |
35 | deinit {
36 | refreshSource?.remove(Sink(owner: self))
37 | }
38 |
39 | override func rawGetValue() -> Value {
40 | return _value
41 | }
42 |
43 | override func rawSetValue(_ value: Value) {
44 | setter(value)
45 | _value = getter()
46 | }
47 |
48 | public func refresh() {
49 | self.value = getter()
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Sources/Connector.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Connector.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2015-11-30.
6 | // Copyright © 2015–2017 Károly Lőrentey.
7 | //
8 |
9 | extension Connection {
10 | /// Put this connection into `connector`. The connector will disconnect the connection when it is deallocated.
11 | @discardableResult
12 | public func putInto(_ connector: Connector) -> Connection {
13 | connector.add(self)
14 | return self
15 | }
16 | }
17 |
18 | /// A class for controlling the lifecycle of connections.
19 | /// The connector owns a set of connections and forces them to disconnect when it is deallocated.
20 | public class Connector {
21 | private var connections: [Connection] = []
22 |
23 | public init() {}
24 |
25 | deinit {
26 | disconnect()
27 | }
28 |
29 | fileprivate func add(_ connection: Connection) {
30 | connections.append(connection)
31 | }
32 |
33 | public func disconnect() {
34 | let cs = connections
35 | connections.removeAll()
36 | for c in cs {
37 | c.disconnect()
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Sources/DependentValue.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DependentValue.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2017-04-23.
6 | // Copyright © 2015–2017 Károly Lőrentey.
7 | //
8 |
9 | infix operator <--
10 |
11 | public func <-- (target: DependentValue, source: Source) where Source.Value == Value {
12 | target.origin = source.anyObservableValue
13 | }
14 |
15 | public class DependentValue {
16 | private let setter: (Value) -> ()
17 | private var transactions: Int = 0
18 | private var pending: Value?
19 |
20 | internal var origin: AnyObservableValue? {
21 | didSet {
22 | receive(.beginTransaction)
23 | oldValue?.remove(Sink(owner: self))
24 | if let origin = origin {
25 | pending = origin.value
26 | origin.add(Sink(owner: self))
27 | }
28 | receive(.endTransaction)
29 | }
30 | }
31 |
32 | private struct Sink: UniqueOwnedSink {
33 | typealias Owner = DependentValue
34 | unowned(unsafe) let owner: Owner
35 | func receive(_ update: ValueUpdate) {
36 | owner.receive(update)
37 | }
38 | }
39 |
40 | public init(setter: @escaping (Value) -> ()) {
41 | self.setter = setter
42 | self.origin = nil
43 | }
44 |
45 | public init(origin: Origin, setter: @escaping (Value) -> ()) where Origin.Value == Value {
46 | self.setter = setter
47 | self.origin = origin.anyObservableValue
48 | origin.add(Sink(owner: self))
49 | }
50 |
51 | deinit {
52 | origin?.remove(Sink(owner: self))
53 | }
54 |
55 | func receive(_ update: ValueUpdate) {
56 | switch update {
57 | case .beginTransaction:
58 | transactions += 1
59 | case .change(let change):
60 | pending = change.new
61 | case .endTransaction:
62 | transactions -= 1
63 | if transactions == 0, let pending = pending {
64 | self.pending = nil
65 | setter(pending)
66 | }
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/Sources/DispatchSource.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DispatchSource.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2016-10-24.
6 | // Copyright © 2015–2017 Károly Lőrentey.
7 | //
8 |
9 | import Foundation
10 |
11 | extension SourceType {
12 | public func dispatch(on queue: DispatchQueue) -> AnySource {
13 | return TransformedSource(input: self, transform: SinkTransformForDispatchQueue(queue)).anySource
14 | }
15 |
16 | public func dispatch(on queue: OperationQueue) -> AnySource {
17 | return TransformedSource(input: self, transform: SinkTransformForOperationQueue(queue)).anySource
18 | }
19 | }
20 |
21 | final class SinkTransformForDispatchQueue: SinkTransform {
22 | typealias Input = Value
23 | typealias Output = Value
24 |
25 | let queue: DispatchQueue
26 |
27 | init(_ queue: DispatchQueue) {
28 | self.queue = queue
29 | }
30 |
31 | func apply(_ input: Value, _ sink: Sink) where Sink.Value == Value {
32 | queue.async {
33 | sink.receive(input)
34 | }
35 | }
36 | }
37 |
38 | final class SinkTransformForOperationQueue: SinkTransform {
39 | typealias Input = Value
40 | typealias Output = Value
41 |
42 | let queue: OperationQueue
43 |
44 | init(_ queue: OperationQueue) {
45 | self.queue = queue
46 | }
47 |
48 | func apply(_ input: Value, _ sink: Sink) where Sink.Value == Value {
49 | if OperationQueue.current == queue {
50 | sink.receive(input)
51 | }
52 | else {
53 | queue.addOperation {
54 | sink.receive(input)
55 | }
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Sources/DistinctUnion.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DistinctUnion.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2016-10-04.
6 | // Copyright © 2015–2017 Károly Lőrentey.
7 | //
8 |
9 | extension ObservableArrayType where Element: Hashable {
10 | /// Returns an observable set that contains the same elements as this array.
11 | public func distinctUnion() -> AnyObservableSet {
12 | return DistinctUnion(self).anyObservableSet
13 | }
14 | }
15 |
16 | private class DistinctUnion: _BaseObservableSet
17 | where Input.Element: Hashable {
18 | typealias Element = Input.Element
19 | typealias Change = SetChange
20 |
21 | private struct DistinctSink: UniqueOwnedSink {
22 | typealias Owner = DistinctUnion
23 |
24 | unowned(unsafe) let owner: Owner
25 |
26 | func receive(_ update: ArrayUpdate) {
27 | owner.apply(update)
28 | }
29 | }
30 |
31 | private let input: Input
32 | private var members = Dictionary()
33 |
34 | init(_ input: Input) {
35 | self.input = input
36 | super.init()
37 | for element in input.value {
38 | _ = self.add(element)
39 | }
40 | input.updates.add(DistinctSink(owner: self))
41 | }
42 |
43 | deinit {
44 | input.updates.remove(DistinctSink(owner: self))
45 | }
46 |
47 | func apply(_ update: ArrayUpdate) {
48 | switch update {
49 | case .beginTransaction:
50 | beginTransaction()
51 | case .change(let change):
52 | var setChange = SetChange()
53 | for mod in change.modifications {
54 | mod.forEachOldElement {
55 | if remove($0) {
56 | setChange.remove($0)
57 | }
58 | }
59 | mod.forEachNewElement {
60 | if add($0) {
61 | setChange.insert($0)
62 | }
63 | }
64 | }
65 | if !setChange.isEmpty {
66 | sendChange(setChange)
67 | }
68 | case .endTransaction:
69 | endTransaction()
70 | }
71 | }
72 |
73 | private func add(_ element: Element) -> Bool {
74 | if let old = self.members[element] {
75 | self.members[element] = old + 1
76 | return false
77 | }
78 | self.members[element] = 1
79 | return true
80 | }
81 |
82 | private func remove(_ element: Element) -> Bool {
83 | let old = self.members[element]!
84 | if old == 1 {
85 | self.members[element] = nil
86 | return true
87 | }
88 | self.members[element] = old - 1
89 | return false
90 | }
91 |
92 | override var isBuffered: Bool { return true }
93 | override var count: Int { return value.count }
94 | override var value: Set { return Set(members.keys) }
95 | override func contains(_ element: Element) -> Bool { return members[element] != nil }
96 | override func isSubset(of other: Set) -> Bool {
97 | guard count <= other.count else { return false }
98 | for (key, _) in members {
99 | guard other.contains(key) else { return false }
100 | }
101 | return true
102 | }
103 | override func isSuperset(of other: Set) -> Bool {
104 | guard count >= other.count else { return false }
105 | for element in other {
106 | guard members[element] != nil else { return false }
107 | }
108 | return true
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/Sources/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | $(VERSION_STRING)
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Sources/Locks.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Locks.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2015-12-01.
6 | // Copyright © 2015–2017 Károly Lőrentey.
7 | //
8 |
9 | import Foundation
10 |
11 | internal protocol Lockable {
12 | func lock()
13 | func unlock()
14 | func withLock(_ block: () throws -> Result) rethrows -> Result
15 | }
16 |
17 | extension Lockable {
18 | func withLock(_ block: () throws -> Result) rethrows -> Result {
19 | lock()
20 | defer { unlock() }
21 | return try block()
22 | }
23 | }
24 |
25 | struct Lock: Lockable {
26 | private let _lock: LockImplementation
27 |
28 | init() {
29 | if #available(macOS 10.12, iOS 10, watchOS 3.0, tvOS 10.0, *) {
30 | self._lock = UnfairLock()
31 | }
32 | else {
33 | self._lock = PosixMutex()
34 | }
35 | }
36 | func lock() { _lock.lock() }
37 | func unlock() { _lock.unlock() }
38 | }
39 |
40 | private class LockImplementation: Lockable {
41 | init() {}
42 |
43 | func lock() {}
44 | func unlock() {}
45 | }
46 |
47 | @available(macOS 10.12, iOS 10, watchOS 3.0, tvOS 10.0, *)
48 | private final class UnfairLock: LockImplementation {
49 | private var _lock = os_unfair_lock()
50 |
51 | override func lock() {
52 | os_unfair_lock_lock(&_lock)
53 | }
54 |
55 | override func unlock() {
56 | os_unfair_lock_unlock(&_lock)
57 | }
58 | }
59 |
60 | private final class PosixMutex: LockImplementation {
61 | private var mutex = pthread_mutex_t()
62 |
63 | override init() {
64 | let result = pthread_mutex_init(&mutex, nil)
65 | precondition(result == 0)
66 | }
67 |
68 | deinit {
69 | let result = pthread_mutex_destroy(&mutex)
70 | precondition(result == 0)
71 | }
72 |
73 | override func lock() {
74 | let result = pthread_mutex_lock(&mutex)
75 | precondition(result == 0)
76 | }
77 |
78 | override func unlock() {
79 | let result = pthread_mutex_unlock(&mutex)
80 | precondition(result == 0)
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/Sources/MergedSource.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MergedSource.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2015-12-04.
6 | // Copyright © 2015–2017 Károly Lőrentey.
7 | //
8 |
9 | import SipHash
10 |
11 | extension Sequence where Element: SourceType {
12 | public func gather() -> MergedSource {
13 | return MergedSource(sources: self)
14 | }
15 | }
16 |
17 | extension SourceType {
18 | /// Returns a source that merges self with `source`. The returned source will forward all values sent by either
19 | /// of its two input sources to its own connected sinks.
20 | ///
21 | /// It is fine to chain multiple merges together: `MergedSource` has its own, specialized `merge` method to
22 | /// collapse multiple merges into a single source.
23 | public func merged(with source: S) -> MergedSource where S.Value == Value {
24 | return MergedSource(sources: [self.anySource, source.anySource])
25 | }
26 |
27 | public static func merge(_ sources: Self...) -> MergedSource {
28 | return MergedSource(sources: sources.map { s in s.anySource })
29 | }
30 |
31 | public static func merge(_ sources: S) -> MergedSource where S.Iterator.Element == Self {
32 | return MergedSource(sources: sources.map { s in s.anySource })
33 | }
34 | }
35 |
36 | /// A Source that receives all values from a set of input sources and forwards all to its own connected sinks.
37 | ///
38 | /// Note that MergedSource only connects to its input sources while it has at least one connection of its own.
39 | public final class MergedSource: SignalerSource {
40 | public typealias SourceValue = Value
41 |
42 | private let inputs: [AnySource]
43 |
44 | /// Initializes a new merged source with `sources` as its input sources.
45 | public init(sources: S) where S.Iterator.Element: SourceType, S.Iterator.Element.Value == Value {
46 | self.inputs = sources.map { $0.anySource }
47 | }
48 |
49 | override func activate() {
50 | for i in 0 ..< inputs.count {
51 | inputs[i].add(MergedSink(source: self, index: i))
52 | }
53 | }
54 |
55 | override func deactivate() {
56 | for i in 0 ..< inputs.count {
57 | inputs[i].remove(MergedSink(source: self, index: i))
58 | }
59 | }
60 |
61 | fileprivate func receive(_ value: Value, from index: Int) {
62 | signal.send(value)
63 | }
64 |
65 | /// Returns a new MergedSource that merges the same sources as self but also listens to `source`.
66 | /// The returned source will forward all values sent by either of its input sources to its own connected sinks.
67 | public func merged(with source: Source) -> MergedSource where Source.Value == Value {
68 | return MergedSource(sources: self.inputs + [source.anySource])
69 | }
70 | }
71 |
72 | private struct MergedSink: SinkType, SipHashable {
73 | let source: MergedSource
74 | let index: Int
75 |
76 | func receive(_ value: Value) {
77 | source.receive(value, from: index)
78 | }
79 |
80 | func appendHashes(to hasher: inout SipHasher) {
81 | hasher.append(ObjectIdentifier(source))
82 | hasher.append(index)
83 | }
84 |
85 | static func ==(left: MergedSink, right: MergedSink) -> Bool {
86 | return left.source === right.source && left.index == right.index
87 | }
88 | }
89 |
90 |
91 |
--------------------------------------------------------------------------------
/Sources/NSButton Glue.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSButton Glue.swift
3 | // macOS
4 | //
5 | // Created by Károly Lőrentey on 2017-09-05.
6 | // Copyright © 2017 Károly Lőrentey. All rights reserved.
7 | //
8 |
9 | #if os(macOS)
10 | import AppKit
11 |
12 | extension NSButton {
13 | @objc open dynamic override var glue: GlueForNSButton { return _glue() }
14 | }
15 |
16 | public func <-- (target: GlueForNSButton.StateReceiver, model: V) where V.Value == NSControl.StateValue {
17 | target.glue.model = model.anyUpdatableValue
18 | }
19 |
20 | public func <-- (target: GlueForNSButton.StateReceiver, model: B) where B.Value == Bool {
21 | target.glue.model = model.map({ $0 ? .on : .off }, inverse: { $0 == .off ? false : true })
22 | }
23 |
24 | open class GlueForNSButton: GlueForNSControl {
25 | private var object: NSButton { return owner as! NSButton }
26 |
27 | public struct StateReceiver {
28 | let glue: GlueForNSButton
29 | }
30 |
31 | public var state: StateReceiver { return StateReceiver(glue: self) }
32 |
33 | private let modelConnector = Connector()
34 | fileprivate var model: AnyUpdatableValue? {
35 | didSet {
36 | modelConnector.disconnect()
37 | if object.target === self {
38 | object.target = nil
39 | object.action = nil
40 | }
41 | if let model = model {
42 | object.target = self
43 | object.action = #selector(GlueForNSButton.buttonAction(_:))
44 | modelConnector.connect(model.values) { [unowned self] value in
45 | self.object.state = value
46 | }
47 | }
48 | }
49 | }
50 |
51 | @IBAction func buttonAction(_ sender: NSButton) {
52 | self.model?.value = sender.state
53 | }
54 | }
55 | #endif
56 |
--------------------------------------------------------------------------------
/Sources/NSNotificationCenter Support.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSNotificationCenter Support.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2015-11-30.
6 | // Copyright © 2015–2017 Károly Lőrentey.
7 | //
8 |
9 | import Foundation
10 |
11 | extension NotificationCenter {
12 | open override var glue: GlueForNotificationCenter {
13 | return _glue()
14 | }
15 | }
16 |
17 | open class GlueForNotificationCenter: GlueForNSObject {
18 | private var object: NotificationCenter { return owner as! NotificationCenter }
19 |
20 | /// Creates a Source that observes the specified notifications and forwards it to its connected sinks.
21 | ///
22 | /// The returned source holds strong references to the notification center and the sender (if any).
23 | /// The source will only observe the notification while a sink is actually connected.
24 | ///
25 | /// - Parameter name: The name of the notification to observe.
26 | /// - Parameter sender: The sender of the notifications to observe, or nil for any object. This parameter is nil by default.
27 | /// - 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.
28 | /// - Returns: A Source that triggers when the specified notification is posted.
29 | public func source(forName name: NSNotification.Name, sender: AnyObject? = nil, queue: OperationQueue? = nil) -> AnySource {
30 | return NotificationSource(center: object, name: name, sender: sender, queue: queue).anySource
31 | }
32 | }
33 |
34 | private class NotificationSource: SignalerSource {
35 | let center: NotificationCenter
36 | let name: NSNotification.Name
37 | let sender: AnyObject?
38 | let queue: OperationQueue?
39 |
40 | init(center: NotificationCenter, name: NSNotification.Name, sender: AnyObject?, queue: OperationQueue?) {
41 | self.center = center
42 | self.name = name
43 | self.sender = sender
44 | self.queue = queue
45 | super.init()
46 | }
47 |
48 | override func activate() {
49 | center.addObserver(self, selector: #selector(didReceive(_:)), name: name, object: sender)
50 | }
51 |
52 | override func deactivate() {
53 | center.removeObserver(self, name: name, object: sender)
54 | }
55 |
56 | @objc private func didReceive(_ notification: Notification) {
57 | if let queue = queue {
58 | queue.addOperation {
59 | self.signal.send(notification)
60 | }
61 | }
62 | else {
63 | self.signal.send(notification)
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Sources/NSPopUpButton Glue.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSPopUpButton Glue.swift
3 | // macOS
4 | //
5 | // Created by Károly Lőrentey on 2017-09-05.
6 | // Copyright © 2017 Károly Lőrentey. All rights reserved.
7 | //
8 |
9 | #if os(macOS)
10 | import AppKit
11 |
12 | extension NSPopUpButton {
13 | @objc open dynamic override var glue: GlueForNSPopUpButton { return _glue() }
14 | }
15 |
16 | public func <-- (target: GlueForNSPopUpButton, choices: NSPopUpButton.Choices) {
17 | target.setChoices(choices)
18 | }
19 |
20 | extension NSPopUpButton {
21 | public struct Choices {
22 | let model: AnyUpdatableValue
23 | let values: AnyObservableArray<(label: String, value: Value)>
24 |
25 | public init(model: U, values: C) where U.Value == Value, C.Element == (label: String, value: Value) {
26 | self.model = model.anyUpdatableValue
27 | self.values = values.anyObservableArray
28 | }
29 |
30 | public init(model: U, values: S) where U.Value == Value, S.Element == (label: String, value: Value) {
31 | self.model = model.anyUpdatableValue
32 | self.values = AnyObservableArray.constant(Array(values))
33 | }
34 |
35 | public init(model: U, values: DictionaryLiteral) where U.Value == Value {
36 | self.model = model.anyUpdatableValue
37 | self.values = AnyObservableArray.constant(Array(values.map { ($0.key, $0.value) }))
38 | }
39 | }
40 | }
41 |
42 | open class GlueForNSPopUpButton: GlueForNSButton {
43 | private var object: NSPopUpButton { return owner as! NSPopUpButton }
44 |
45 | private var valueConnection: Connection? = nil
46 | private var choicesConnection: Connection? = nil
47 | private var update: (Any?) -> Void = { _ in }
48 |
49 | fileprivate func setChoices(_ choices: NSPopUpButton.Choices) {
50 |
51 | valueConnection?.disconnect()
52 | choicesConnection?.disconnect()
53 |
54 | update = { value in if let value = value as? Value { choices.model.value = value } }
55 |
56 | choicesConnection = choices.values.anyObservableValue.values.subscribe { [unowned self] choices in
57 | let menu = NSMenu()
58 | choices.forEach { choice in
59 | let item = NSMenuItem(title: choice.label, action: #selector(GlueForNSPopUpButton.choiceAction(_:)), keyEquivalent: "")
60 | item.target = self
61 | item.representedObject = choice.value
62 | menu.addItem(item)
63 | }
64 | self.object.menu = menu
65 | }
66 |
67 | valueConnection = choices.model.values.subscribe { [unowned self] newValue in
68 | if let item = self.object.menu?.items.first(where: { $0.representedObject as? Value == newValue }) {
69 | if self.object.selectedItem != item {
70 | self.object.select(item)
71 | }
72 | }
73 | else {
74 | self.object.select(nil)
75 | }
76 | }
77 | }
78 |
79 | @IBAction func choiceAction(_ sender: NSMenuItem) {
80 | update(sender.representedObject)
81 | }
82 | }
83 | #endif
84 |
--------------------------------------------------------------------------------
/Sources/NSTextField Glue.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSTextField Glue.swift
3 | // macOS
4 | //
5 | // Created by Károly Lőrentey on 2017-09-05.
6 | // Copyright © 2017 Károly Lőrentey. All rights reserved.
7 | //
8 |
9 | #if os(macOS)
10 | import AppKit
11 |
12 | extension NSTextField {
13 | @objc open dynamic override var glue: GlueForNSTextField { return _glue() }
14 | }
15 |
16 | public func <-- (target: GlueForNSTextField.ValidatingValueReceiver, model: V) where V.Value: LosslessStringConvertible {
17 | target.glue.setModel(model)
18 | }
19 |
20 | open class GlueForNSTextField: GlueForNSControl {
21 | private var object: NSTextField { return owner as! NSTextField }
22 | private var delegate: Any? = nil
23 |
24 | public struct ValidatingValueReceiver { let glue: GlueForNSTextField }
25 | public var value: ValidatingValueReceiver { return ValidatingValueReceiver(glue: self) }
26 |
27 | fileprivate func setModel(_ model: V) where V.Value: LosslessStringConvertible {
28 | if let delegate = self.delegate as? GlueKitTextFieldDelegate {
29 | delegate.model = model.anyUpdatableValue
30 | }
31 | else {
32 | let delegate = GlueKitTextFieldDelegate(object, model)
33 | self.delegate = delegate
34 | }
35 | }
36 | }
37 |
38 | class GlueKitTextFieldDelegate: NSObject, NSTextFieldDelegate {
39 | unowned let view: NSTextField
40 | var model: AnyUpdatableValue {
41 | didSet { reconnect() }
42 | }
43 |
44 | init(_ view: NSTextField, _ model: V) where V.Value == Value {
45 | self.view = view
46 | self.model = model.anyUpdatableValue
47 | super.init()
48 | reconnect()
49 | }
50 |
51 | private var modelConnection: Connection? = nil
52 | private func reconnect() {
53 | view.delegate = self
54 | modelConnection?.disconnect()
55 | modelConnection = model.values.subscribe { [unowned self] value in
56 | self.view.stringValue = "\(value)"
57 | }
58 | }
59 |
60 | func control(_ control: NSControl, textShouldEndEditing fieldEditor: NSText) -> Bool {
61 | return Value(view.stringValue) != nil
62 | }
63 |
64 | override func controlTextDidEndEditing(_ obj: Notification) {
65 | if let value = Value(view.stringValue) {
66 | model.value = value
67 | }
68 | else {
69 | view.stringValue = "\(model.value)"
70 | }
71 | }
72 |
73 | func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {
74 | guard commandSelector == #selector(NSResponder.cancelOperation(_:)) else { return false }
75 | view.stringValue = "\(model.value)"
76 | //textView.window?.makeFirstResponder(nil)
77 | return true
78 | }
79 | }
80 | #endif
81 |
--------------------------------------------------------------------------------
/Sources/ObservableContains.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObservableContains.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2016-10-02.
6 | // Copyright © 2015–2017 Károly Lőrentey.
7 | //
8 |
9 | extension ObservableSetType {
10 | public func observableContains(_ member: Element) -> AnyObservableValue {
11 | return ObservableContains(input: self, member: member).anyObservableValue
12 | }
13 | }
14 |
15 | private final class ObservableContains: _AbstractObservableValue {
16 | let input: Input
17 | let member: Input.Element
18 | let _updates: AnySource>
19 |
20 | init(input: Input, member: Input.Element) {
21 | self.input = input
22 | self.member = member
23 | self._updates = input.updates.flatMap { update in
24 | update.flatMap {
25 | let old = $0.removed.contains(member)
26 | let new = $0.inserted.contains(member)
27 | if old == new {
28 | return nil
29 | }
30 | else {
31 | return ValueChange(from: old, to: new)
32 | }
33 | }
34 | }
35 | }
36 |
37 | override var value: Bool {
38 | return input.contains(member)
39 | }
40 |
41 | override func add(_ sink: Sink) where Sink.Value == Update {
42 | _updates.add(sink)
43 | }
44 |
45 | @discardableResult
46 | override func remove(_ sink: Sink) -> Sink where Sink.Value == Update {
47 | return _updates.remove(sink)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Sources/ObservableType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObservableValueType.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2016-10-04.
6 | // Copyright © 2015–2017 Károly Lőrentey.
7 | //
8 |
9 | public protocol ObservableType {
10 | associatedtype Value
11 | associatedtype Change: ChangeType where Change.Value == Value
12 |
13 | /// The current value of this observable.
14 | var value: Value { get }
15 |
16 | func add(_ sink: Sink) where Sink.Value == Update
17 |
18 | @discardableResult
19 | func remove(_ sink: Sink) -> Sink where Sink.Value == Update
20 | }
21 |
22 | extension ObservableType {
23 | /// A source that reports update transaction events for this observable.
24 | public var updates: UpdateSource {
25 | return UpdateSource(owner: self)
26 | }
27 | }
28 |
29 | extension ObservableType {
30 | /// A source that sends an empty value whenever the observable completes a transaction.
31 | public var tick: AnySource {
32 | return self.updates.flatMap { if case .endTransaction = $0 { return () }; return nil }
33 | }
34 | }
35 |
36 | public struct UpdateSource: SourceType {
37 | public typealias Value = Update
38 |
39 | private let owner: Observable
40 |
41 | init(owner: Observable) {
42 | self.owner = owner
43 | }
44 |
45 | public func add(_ sink: Sink) where Sink.Value == Value {
46 | owner.add(sink)
47 | }
48 |
49 | @discardableResult
50 | public func remove(_ sink: Sink) -> Sink where Sink.Value == Value {
51 | return owner.remove(sink)
52 | }
53 | }
54 |
55 | public protocol UpdatableType: ObservableType {
56 | /// The current value of this observable.
57 | ///
58 | /// The setter is nonmutating because the value ultimately needs to be stored in a reference type anyway.
59 | var value: Value { get nonmutating set }
60 |
61 | func apply(_ update: Update)
62 | }
63 |
64 | extension UpdatableType {
65 | public func withTransaction(_ body: () -> Result) -> Result {
66 | apply(.beginTransaction)
67 | defer { apply(.endTransaction) }
68 | return body()
69 | }
70 |
71 | public func apply(_ change: Change) {
72 | if !change.isEmpty {
73 | apply(.beginTransaction)
74 | apply(.change(change))
75 | apply(.endTransaction)
76 | }
77 | }
78 | }
79 |
80 | extension ObservableType {
81 | public func subscribe(to updatable: Updatable) -> Connection
82 | where Updatable.Change == Change {
83 | updatable.apply(.beginTransaction)
84 | updatable.apply(.change(Change(from: updatable.value, to: self.value)))
85 | let connection = updates.subscribe { update in updatable.apply(update) }
86 | updatable.apply(.endTransaction)
87 | return connection
88 | }
89 | }
90 |
91 | extension Connector {
92 | @discardableResult
93 | public func subscribe(_ observable: Observable, to sink: @escaping (Update) -> Void) -> Connection {
94 | return observable.updates.subscribe(sink).putInto(self)
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/Sources/OwnedSink.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StrongMethodSink.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2016-10-24.
6 | // Copyright © 2015–2017 Károly Lőrentey.
7 | //
8 |
9 | import SipHash
10 |
11 | protocol UniqueOwnedSink: SinkType {
12 | associatedtype Owner: AnyObject
13 |
14 | var owner: Owner { get }
15 | }
16 |
17 | extension UniqueOwnedSink {
18 | var hashValue: Int {
19 | return ObjectIdentifier(owner).hashValue
20 | }
21 |
22 | static func ==(left: Self, right: Self) -> Bool {
23 | return left.owner === right.owner
24 | }
25 | }
26 |
27 | protocol OwnedSink: SinkType, SipHashable {
28 | associatedtype Owner: AnyObject
29 | associatedtype Identifier: Hashable
30 |
31 | var owner: Owner { get }
32 | var identifier: Identifier { get }
33 | }
34 |
35 | extension OwnedSink {
36 | func appendHashes(to hasher: inout SipHasher) {
37 | hasher.append(ObjectIdentifier(owner))
38 | hasher.append(identifier)
39 | }
40 |
41 | static func ==(left: Self, right: Self) -> Bool {
42 | return left.owner === right.owner && left.identifier == right.identifier
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Sources/Reference.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Reference.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2015-12-13.
6 | // Copyright © 2015–2017 Károly Lőrentey.
7 | //
8 |
9 | internal struct UnownedReference {
10 | unowned var value: Target
11 |
12 | init(_ value: Target) {
13 | self.value = value
14 | }
15 | }
16 |
17 |
18 |
--------------------------------------------------------------------------------
/Sources/SetChange.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SetChange.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2016-08-12.
6 | // Copyright © 2015–2017 Károly Lőrentey.
7 | //
8 |
9 | public struct SetChange: ChangeType {
10 | public typealias Value = Set
11 |
12 | public private(set) var removed: Set
13 | public private(set) var inserted: Set
14 |
15 | public init(removed: Set = [], inserted: Set = []) {
16 | self.inserted = inserted
17 | self.removed = removed
18 | }
19 |
20 | public init(from oldValue: Value, to newValue: Value) {
21 | self.removed = oldValue.subtracting(newValue)
22 | self.inserted = newValue.subtracting(oldValue)
23 | }
24 |
25 | public var isEmpty: Bool {
26 | return inserted.isEmpty && removed.isEmpty
27 | }
28 |
29 | public func apply(on value: inout Set) {
30 | value.subtract(removed)
31 | value = inserted.union(value)
32 | }
33 |
34 | public func apply(on value: Value) -> Value {
35 | return inserted.union(value.subtracting(removed))
36 | }
37 |
38 | public mutating func remove(_ element: Element) {
39 | self.inserted.remove(element)
40 | self.removed.update(with: element)
41 | }
42 |
43 | public mutating func insert(_ element: Element) {
44 | self.inserted.update(with: element)
45 | }
46 |
47 | public mutating func merge(with next: SetChange) {
48 | removed = next.removed.union(removed)
49 | inserted = next.inserted.union(inserted.subtracting(next.removed))
50 | }
51 |
52 | public func merged(with next: SetChange) -> SetChange {
53 | return SetChange(removed: next.removed.union(removed),
54 | inserted: next.inserted.union(inserted.subtracting(next.removed)))
55 | }
56 |
57 | public func reversed() -> SetChange {
58 | return SetChange(removed: inserted, inserted: removed)
59 | }
60 |
61 | public func removingEqualChanges() -> SetChange {
62 | let intersection = removed.intersection(inserted)
63 | if intersection.isEmpty {
64 | return self
65 | }
66 | return SetChange(removed: removed.subtracting(intersection), inserted: inserted.subtracting(intersection))
67 | }
68 | }
69 |
70 | extension Set {
71 | public mutating func apply(_ change: SetChange) {
72 | self.subtract(change.removed)
73 | for e in change.inserted {
74 | self.update(with: e)
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Sources/SetFolding.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SetFolding.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2016-10-09.
6 | // Copyright © 2015–2017 Károly Lőrentey.
7 | //
8 |
9 | extension ObservableSetType {
10 | /// Returns an observable whose value is always equal to `self.value.reduce(initial, add)`.
11 | ///
12 | /// - Parameter initial: The accumulation starts with this initial value.
13 | /// - Parameter add: A closure that adds an element of the set into an accumulated value.
14 | /// - Parameter remove: A closure that cancels the effect of an earlier `add`.
15 | /// - Returns: An observable value for the reduction of this set.
16 | ///
17 | /// - Note: Elements are added and removed in no particular order.
18 | /// (I.e., the underlying binary operation over `Result` must form an abelian group.)
19 | ///
20 | /// - SeeAlso: `sum()` which returns a reduction using addition.
21 | public func reduce(_ initial: Result, add: @escaping (Result, Element) -> Result, remove: @escaping (Result, Element) -> Result) -> AnyObservableValue {
22 | return SetFoldingByTwoWayFunction(parent: self, initial: initial, add: add, remove: remove).anyObservableValue
23 | }
24 | }
25 |
26 | extension ObservableSetType where Element: BinaryInteger {
27 | /// Return the (observable) sum of the elements contained in this set.
28 | public func sum() -> AnyObservableValue {
29 | return reduce(0, add: +, remove: -)
30 | }
31 | }
32 |
33 | private class SetFoldingByTwoWayFunction: _BaseObservableValue {
34 | private struct FoldingSink: UniqueOwnedSink {
35 | typealias Owner = SetFoldingByTwoWayFunction
36 |
37 | unowned(unsafe) let owner: Owner
38 |
39 | func receive(_ update: SetUpdate) {
40 | owner.applyUpdate(update)
41 | }
42 | }
43 |
44 | let parent: Parent
45 | let add: (Value, Parent.Element) -> Value
46 | let remove: (Value, Parent.Element) -> Value
47 |
48 | private var _value: Value
49 |
50 | init(parent: Parent, initial: Value, add: @escaping (Value, Parent.Element) -> Value, remove: @escaping (Value, Parent.Element) -> Value) {
51 | self.parent = parent
52 | self.add = add
53 | self.remove = remove
54 |
55 | self._value = parent.value.reduce(initial, add)
56 |
57 | super.init()
58 |
59 | parent.add(FoldingSink(owner: self))
60 | }
61 |
62 | deinit {
63 | parent.remove(FoldingSink(owner: self))
64 | }
65 |
66 | override var value: Value {
67 | return _value
68 | }
69 |
70 | func applyUpdate(_ update: SetUpdate) {
71 | switch update {
72 | case .beginTransaction:
73 | beginTransaction()
74 | case .change(let change):
75 | let old = _value
76 | for old in change.removed { _value = remove(_value, old) }
77 | for new in change.inserted { _value = add(_value, new) }
78 | sendChange(ValueChange(from: old, to: _value))
79 | case .endTransaction:
80 | endTransaction()
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/Sources/SetGatheringSource.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SetGatheringSource.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2017-09-05.
6 | // Copyright © 2017 Károly Lőrentey. All rights reserved.
7 | //
8 |
9 | extension ObservableSetType where Element: SourceType {
10 | public func gather() -> AnySource {
11 | return SetGatheringSource(self).anySource
12 | }
13 | }
14 |
15 | private class SetGatheringSource: _AbstractSource
16 | where Origin.Element: SourceType, Origin.Element.Value == Value {
17 | let origin: Origin
18 | var sinks: Set> = []
19 |
20 | private struct GatherSink: UniqueOwnedSink {
21 | typealias Owner = SetGatheringSource
22 | unowned let owner: Owner
23 |
24 | func receive(_ value: SetUpdate) {
25 | guard case let .change(change) = value else { return }
26 | change.removed.forEach { source in
27 | for sink in owner.sinks {
28 | source.remove(sink)
29 | }
30 | }
31 | change.inserted.forEach { source in
32 | for sink in owner.sinks {
33 | source.add(sink)
34 | }
35 | }
36 | }
37 | }
38 |
39 | init(_ origin: Origin) {
40 | self.origin = origin
41 | }
42 |
43 | override func add(_ sink: Sink) where Sink.Value == Value {
44 | if sinks.isEmpty {
45 | origin.add(GatherSink(owner: self))
46 | }
47 | let new = sinks.insert(sink.anySink).inserted
48 | precondition(new)
49 | for source in origin.value {
50 | source.add(sink)
51 | }
52 | }
53 |
54 | @discardableResult
55 | override func remove(_ sink: Sink) -> Sink where Sink.Value == Value {
56 | let result = sinks.remove(sink.anySink)!
57 | for source in origin.value {
58 | source.remove(result)
59 | }
60 | if sinks.isEmpty {
61 | origin.remove(GatherSink(owner: self))
62 | }
63 | return result.opened()!
64 | }
65 | }
66 |
67 |
--------------------------------------------------------------------------------
/Sources/SetMappingBase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SetMappingBase.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2016-10-05.
6 | // Copyright © 2015–2017 Károly Lőrentey.
7 | //
8 |
9 | /// An observable set where the value is internally represented as a dictionary of element multiplicities.
10 | /// This class implements the full `ObservableSetType` protocol, and serves as the base class for several transformations
11 | /// on observable sets.
12 | class SetMappingBase: _BaseObservableSet {
13 | typealias Change = SetChange
14 |
15 | private(set) var contents: [Element: Int] = [:]
16 |
17 | /// Insert `newMember` into `state`, and return true iff it did not previously contain it.
18 | final func insert(_ newMember: Element) -> Bool {
19 | if let count = contents[newMember] {
20 | contents[newMember] = count + 1
21 | return false
22 | }
23 | contents[newMember] = 1
24 | return true
25 | }
26 |
27 | /// Remove a single instance of `newMember` from `state`, and return true iff this was the last instance.
28 | /// - Requires: `self.contains(member)`.
29 | final func remove(_ member: Element) -> Bool {
30 | guard let count = contents[member] else {
31 | fatalError("Inconsistent change: \(member) to be removed is not in result set")
32 | }
33 | if count > 1 {
34 | contents[member] = count - 1
35 | return false
36 | }
37 | contents.removeValue(forKey: member)
38 | return true
39 | }
40 |
41 | final override var isBuffered: Bool { return false }
42 | final override var count: Int { return contents.count }
43 | final override var value: Set { return Set(contents.keys) }
44 | final override func contains(_ member: Element) -> Bool { return contents[member] != nil }
45 |
46 | final override func isSubset(of other: Set) -> Bool {
47 | guard other.count >= contents.count else { return false }
48 | for (key, _) in contents {
49 | guard other.contains(key) else { return false }
50 | }
51 | return true
52 | }
53 |
54 | final override func isSuperset(of other: Set) -> Bool {
55 | guard other.count <= contents.count else { return false }
56 | for element in other {
57 | guard contents[element] != nil else { return false }
58 | }
59 | return true
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Sources/SetMappingForSequence.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SetMappingForSequence.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2016-10-07.
6 | // Copyright © 2015–2017 Károly Lőrentey.
7 | //
8 |
9 | extension ObservableSetType {
10 | public func flatMap(_ key: @escaping (Element) -> Result) -> AnyObservableSet where Result.Iterator.Element: Hashable {
11 | return SetMappingForSequence(parent: self, key: key).anyObservableSet
12 | }
13 | }
14 |
15 | class SetMappingForSequence: SetMappingBase
16 | where Result.Iterator.Element: Hashable {
17 | typealias Element = Result.Iterator.Element
18 |
19 | private struct ParentSink: UniqueOwnedSink {
20 | typealias Owner = SetMappingForSequence
21 |
22 | unowned(unsafe) let owner: Owner
23 |
24 | func receive(_ update: SetUpdate) {
25 | owner.apply(update)
26 | }
27 | }
28 |
29 | let parent: Parent
30 | let key: (Parent.Element) -> Result
31 |
32 | init(parent: Parent, key: @escaping (Parent.Element) -> Result) {
33 | self.parent = parent
34 | self.key = key
35 | super.init()
36 | for e in parent.value {
37 | for new in key(e) {
38 | _ = self.insert(new)
39 | }
40 | }
41 | parent.add(ParentSink(owner: self))
42 | }
43 |
44 | deinit {
45 | parent.remove(ParentSink(owner: self))
46 | }
47 |
48 | func apply(_ update: SetUpdate) {
49 | switch update {
50 | case .beginTransaction:
51 | beginTransaction()
52 | case .change(let change):
53 | var transformedChange = SetChange()
54 | for e in change.removed {
55 | for old in key(e) {
56 | if self.remove(old) {
57 | transformedChange.remove(old)
58 | }
59 | }
60 | }
61 | for e in change.inserted {
62 | for new in key(e) {
63 | if self.insert(new) {
64 | transformedChange.insert(new)
65 | }
66 | }
67 | }
68 | if !transformedChange.isEmpty {
69 | sendChange(transformedChange)
70 | }
71 | case .endTransaction:
72 | endTransaction()
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/Sources/SetMappingForSetField.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SetMappingForSetField.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2016-10-07.
6 | // Copyright © 2015–2017 Károly Lőrentey.
7 | //
8 |
9 | extension ObservableSetType {
10 | public func flatMap(_ key: @escaping (Element) -> Field) -> AnyObservableSet {
11 | return SetMappingForSetField(parent: self, key: key).anyObservableSet
12 | }
13 | }
14 |
15 | class SetMappingForSetField: SetMappingBase {
16 | private struct ParentSink: UniqueOwnedSink {
17 | typealias Owner = SetMappingForSetField
18 |
19 | unowned(unsafe) let owner: Owner
20 |
21 | func receive(_ update: SetUpdate) {
22 | owner.applyParentUpdate(update)
23 | }
24 | }
25 |
26 | private struct FieldSink: UniqueOwnedSink {
27 | typealias Owner = SetMappingForSetField
28 |
29 | unowned(unsafe) let owner: Owner
30 |
31 | func receive(_ update: SetUpdate) {
32 | owner.applyFieldUpdate(update)
33 | }
34 | }
35 |
36 | let parent: Parent
37 | let key: (Parent.Element) -> Field
38 |
39 | init(parent: Parent, key: @escaping (Parent.Element) -> Field) {
40 | self.parent = parent
41 | self.key = key
42 | super.init()
43 | parent.add(ParentSink(owner: self))
44 |
45 | for e in parent.value {
46 | let field = key(e)
47 | field.add(FieldSink(owner: self))
48 | for new in field.value {
49 | _ = self.insert(new)
50 | }
51 | }
52 | }
53 |
54 | deinit {
55 | parent.remove(ParentSink(owner: self))
56 | parent.value.forEach { e in
57 | let field = key(e)
58 | field.remove(FieldSink(owner: self))
59 | }
60 | }
61 |
62 | func applyParentUpdate(_ update: SetUpdate) {
63 | switch update {
64 | case .beginTransaction:
65 | beginTransaction()
66 | case .change(let change):
67 | var transformedChange = SetChange()
68 | for e in change.removed {
69 | let field = key(e)
70 | field.remove(FieldSink(owner: self))
71 | for r in field.value {
72 | if self.remove(r) {
73 | transformedChange.remove(r)
74 | }
75 | }
76 | }
77 | for e in change.inserted {
78 | let field = key(e)
79 | field.add(FieldSink(owner: self))
80 | for i in field.value {
81 | if self.insert(i) {
82 | transformedChange.insert(i)
83 | }
84 | }
85 | }
86 | if !transformedChange.isEmpty {
87 | sendChange(transformedChange)
88 | }
89 | case .endTransaction:
90 | endTransaction()
91 | }
92 | }
93 |
94 | func applyFieldUpdate(_ update: SetUpdate) {
95 | switch update {
96 | case .beginTransaction:
97 | beginTransaction()
98 | case .change(let change):
99 | var transformedChange = SetChange()
100 | for old in change.removed {
101 | if self.remove(old) {
102 | transformedChange.remove(old)
103 | }
104 | }
105 | for new in change.inserted {
106 | if self.insert(new) {
107 | transformedChange.insert(new)
108 | }
109 | }
110 | if !transformedChange.isEmpty {
111 | sendChange(transformedChange)
112 | }
113 | case .endTransaction:
114 | endTransaction()
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/Sources/SetReference.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SetReference.swift
3 | // GlueKit
4 | //
5 | // Created by Károly Lőrentey on 2016-08-17.
6 | // Copyright © 2015–2017 Károly Lőrentey.
7 | //
8 |
9 | extension ObservableValueType where Value: ObservableSetType {
10 | public func unpacked() -> AnyObservableSet {
11 | return UnpackedObservableSetReference(self).anyObservableSet
12 | }
13 | }
14 |
15 | /// A mutable reference to an `AnyObservableSet` that's also an observable set.
16 | /// You can switch to another target set without having to re-register subscribers.
17 | private final class UnpackedObservableSetReference: _BaseObservableSet
18 | where Reference.Value: ObservableSetType {
19 | typealias Target = Reference.Value
20 | typealias Element = Target.Element
21 | typealias Change = SetChange