Skip to content

Commit 84716b3

Browse files
authored
Merge pull request #32 from connor-ricks/feature/buffable-async-publishers
Introduces ⚡️ Fuse as a Combine framework
2 parents 94eae2e + 9906ead commit 84716b3

File tree

14 files changed

+898
-156
lines changed

14 files changed

+898
-156
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Scheme
3+
LastUpgradeVersion = "1500"
4+
version = "1.7">
5+
<BuildAction
6+
parallelizeBuildables = "YES"
7+
buildImplicitDependencies = "YES">
8+
<BuildActionEntries>
9+
<BuildActionEntry
10+
buildForTesting = "YES"
11+
buildForRunning = "YES"
12+
buildForProfiling = "YES"
13+
buildForArchiving = "YES"
14+
buildForAnalyzing = "YES">
15+
<BuildableReference
16+
BuildableIdentifier = "primary"
17+
BlueprintIdentifier = "Fuse"
18+
BuildableName = "Fuse"
19+
BlueprintName = "Fuse"
20+
ReferencedContainer = "container:">
21+
</BuildableReference>
22+
</BuildActionEntry>
23+
</BuildActionEntries>
24+
</BuildAction>
25+
<TestAction
26+
buildConfiguration = "Debug"
27+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
28+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
29+
shouldUseLaunchSchemeArgsEnv = "YES">
30+
<TestPlans>
31+
<TestPlanReference
32+
reference = "container:Tests/FuseTests/Fuse.xctestplan"
33+
default = "YES">
34+
</TestPlanReference>
35+
</TestPlans>
36+
</TestAction>
37+
<LaunchAction
38+
buildConfiguration = "Debug"
39+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
40+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
41+
launchStyle = "0"
42+
useCustomWorkingDirectory = "NO"
43+
ignoresPersistentStateOnLaunch = "NO"
44+
debugDocumentVersioning = "YES"
45+
debugServiceExtension = "internal"
46+
allowLocationSimulation = "YES">
47+
</LaunchAction>
48+
<ProfileAction
49+
buildConfiguration = "Release"
50+
shouldUseLaunchSchemeArgsEnv = "YES"
51+
savedToolIdentifier = ""
52+
useCustomWorkingDirectory = "NO"
53+
debugDocumentVersioning = "YES">
54+
<MacroExpansion>
55+
<BuildableReference
56+
BuildableIdentifier = "primary"
57+
BlueprintIdentifier = "Fuse"
58+
BuildableName = "Fuse"
59+
BlueprintName = "Fuse"
60+
ReferencedContainer = "container:">
61+
</BuildableReference>
62+
</MacroExpansion>
63+
</ProfileAction>
64+
<AnalyzeAction
65+
buildConfiguration = "Debug">
66+
</AnalyzeAction>
67+
<ArchiveAction
68+
buildConfiguration = "Release"
69+
revealArchiveInOrganizer = "YES">
70+
</ArchiveAction>
71+
</Scheme>

.swiftpm/xcode/xcshareddata/xcschemes/swift-nibbles-Package.xcscheme

Lines changed: 9 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
buildForAnalyzing = "YES">
1515
<BuildableReference
1616
BuildableIdentifier = "primary"
17-
BlueprintIdentifier = "Cache"
18-
BuildableName = "Cache"
19-
BlueprintName = "Cache"
17+
BlueprintIdentifier = "Extensions"
18+
BuildableName = "Extensions"
19+
BlueprintName = "Extensions"
2020
ReferencedContainer = "container:">
2121
</BuildableReference>
2222
</BuildActionEntry>
@@ -28,9 +28,9 @@
2828
buildForAnalyzing = "YES">
2929
<BuildableReference
3030
BuildableIdentifier = "primary"
31-
BlueprintIdentifier = "Extensions"
32-
BuildableName = "Extensions"
33-
BlueprintName = "Extensions"
31+
BlueprintIdentifier = "Fuse"
32+
BuildableName = "Fuse"
33+
BlueprintName = "Fuse"
3434
ReferencedContainer = "container:">
3535
</BuildableReference>
3636
</BuildActionEntry>
@@ -62,62 +62,6 @@
6262
ReferencedContainer = "container:">
6363
</BuildableReference>
6464
</BuildActionEntry>
65-
<BuildActionEntry
66-
buildForTesting = "YES"
67-
buildForRunning = "YES"
68-
buildForProfiling = "NO"
69-
buildForArchiving = "NO"
70-
buildForAnalyzing = "YES">
71-
<BuildableReference
72-
BuildableIdentifier = "primary"
73-
BlueprintIdentifier = "CacheTests"
74-
BuildableName = "CacheTests"
75-
BlueprintName = "CacheTests"
76-
ReferencedContainer = "container:">
77-
</BuildableReference>
78-
</BuildActionEntry>
79-
<BuildActionEntry
80-
buildForTesting = "YES"
81-
buildForRunning = "YES"
82-
buildForProfiling = "NO"
83-
buildForArchiving = "NO"
84-
buildForAnalyzing = "YES">
85-
<BuildableReference
86-
BuildableIdentifier = "primary"
87-
BlueprintIdentifier = "ExtensionsTests"
88-
BuildableName = "ExtensionsTests"
89-
BlueprintName = "ExtensionsTests"
90-
ReferencedContainer = "container:">
91-
</BuildableReference>
92-
</BuildActionEntry>
93-
<BuildActionEntry
94-
buildForTesting = "YES"
95-
buildForRunning = "YES"
96-
buildForProfiling = "NO"
97-
buildForArchiving = "NO"
98-
buildForAnalyzing = "YES">
99-
<BuildableReference
100-
BuildableIdentifier = "primary"
101-
BlueprintIdentifier = "HTTPNetworkingTests"
102-
BuildableName = "HTTPNetworkingTests"
103-
BlueprintName = "HTTPNetworkingTests"
104-
ReferencedContainer = "container:">
105-
</BuildableReference>
106-
</BuildActionEntry>
107-
<BuildActionEntry
108-
buildForTesting = "YES"
109-
buildForRunning = "YES"
110-
buildForProfiling = "NO"
111-
buildForArchiving = "NO"
112-
buildForAnalyzing = "YES">
113-
<BuildableReference
114-
BuildableIdentifier = "primary"
115-
BlueprintIdentifier = "IdentifiedTests"
116-
BuildableName = "IdentifiedTests"
117-
BlueprintName = "IdentifiedTests"
118-
ReferencedContainer = "container:">
119-
</BuildableReference>
120-
</BuildActionEntry>
12165
<BuildActionEntry
12266
buildForTesting = "YES"
12367
buildForRunning = "YES"
@@ -145,48 +89,6 @@
14589
default = "YES">
14690
</TestPlanReference>
14791
</TestPlans>
148-
<Testables>
149-
<TestableReference
150-
skipped = "NO">
151-
<BuildableReference
152-
BuildableIdentifier = "primary"
153-
BlueprintIdentifier = "CacheTests"
154-
BuildableName = "CacheTests"
155-
BlueprintName = "CacheTests"
156-
ReferencedContainer = "container:">
157-
</BuildableReference>
158-
</TestableReference>
159-
<TestableReference
160-
skipped = "NO">
161-
<BuildableReference
162-
BuildableIdentifier = "primary"
163-
BlueprintIdentifier = "ExtensionsTests"
164-
BuildableName = "ExtensionsTests"
165-
BlueprintName = "ExtensionsTests"
166-
ReferencedContainer = "container:">
167-
</BuildableReference>
168-
</TestableReference>
169-
<TestableReference
170-
skipped = "NO">
171-
<BuildableReference
172-
BuildableIdentifier = "primary"
173-
BlueprintIdentifier = "HTTPNetworkingTests"
174-
BuildableName = "HTTPNetworkingTests"
175-
BlueprintName = "HTTPNetworkingTests"
176-
ReferencedContainer = "container:">
177-
</BuildableReference>
178-
</TestableReference>
179-
<TestableReference
180-
skipped = "NO">
181-
<BuildableReference
182-
BuildableIdentifier = "primary"
183-
BlueprintIdentifier = "IdentifiedTests"
184-
BuildableName = "IdentifiedTests"
185-
BlueprintName = "IdentifiedTests"
186-
ReferencedContainer = "container:">
187-
</BuildableReference>
188-
</TestableReference>
189-
</Testables>
19092
</TestAction>
19193
<LaunchAction
19294
buildConfiguration = "Debug"
@@ -208,9 +110,9 @@
208110
<MacroExpansion>
209111
<BuildableReference
210112
BuildableIdentifier = "primary"
211-
BlueprintIdentifier = "Cache"
212-
BuildableName = "Cache"
213-
BlueprintName = "Cache"
113+
BlueprintIdentifier = "Stash"
114+
BuildableName = "Stash"
115+
BlueprintName = "Stash"
214116
ReferencedContainer = "container:">
215117
</BuildableReference>
216118
</MacroExpansion>

Package.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,29 @@ let package = Package(
1111
.watchOS(.v9),
1212
],
1313
products: [
14-
.library(name: "Stash", targets: ["Stash"]),
1514
.library(name: "Extensions", targets: ["Extensions"]),
15+
.library(name: "Fuse", targets: ["Fuse"]),
1616
.library(name: "HTTPNetworking", targets: ["HTTPNetworking"]),
1717
.library(name: "Identified", targets: ["Identified"]),
18+
.library(name: "Stash", targets: ["Stash"]),
1819
.plugin(name: "Create TCA Feature", targets: ["Create TCA Feature"])
1920
],
2021
targets: [
21-
.target(name: "Stash"),
22-
.testTarget(name: "StashTests", dependencies: ["Stash"]),
23-
2422
.target(name: "Extensions"),
2523
.testTarget(name: "ExtensionsTests", dependencies: ["Extensions"]),
2624

25+
.target(name: "Fuse"),
26+
.testTarget(name: "FuseTests", dependencies: ["Fuse"]),
27+
2728
.target(name: "HTTPNetworking"),
2829
.testTarget(name: "HTTPNetworkingTests", dependencies: ["HTTPNetworking"]),
2930

3031
.target(name: "Identified"),
3132
.testTarget(name: "IdentifiedTests", dependencies: ["Identified"]),
3233

34+
.target(name: "Stash"),
35+
.testTarget(name: "StashTests", dependencies: ["Stash"]),
36+
3337
.plugin(
3438
name: "Create TCA Feature",
3539
capability: .command(

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ You can then fetch attempt to retrieve from the `Stash` at a later time using th
3333
### ⛓️ Extensions
3434
A collection of useful extensions that I freqeuntly implement across multiple projects.
3535

36+
### ⚡️ Fuse
37+
A collection of useful Combine nibbles.
38+
39+
- A variety of helpful sinks that allow for easier less verbose interactions with Combine publishers.
40+
- A variety of helpful sinks that automatically cleanup after themselves by using a `DisposableBag`.
41+
- `BuffableAsyncPublisher` and `BuffableAsyncThrowingPublisher` which both expose a `values(bufferingStrategy:)` on `Publisher`
42+
- This is a more configurable and powerful version of `values` in Combine that allows converting Combine to an async/await syntax.
43+
3644
### 🕸️ HTTPNetworking
3745
A client that creates and manages requests over the network.
3846

Sources/Extensions/Publisher+Sinks.swift renamed to Sources/Fuse/Extensions/Publisher+Sinks.swift

Lines changed: 3 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -23,41 +23,6 @@
2323
import Foundation
2424
import Combine
2525

26-
// MARK: - DisposableBag
27-
28-
/// A thread safe bag of `AnyCancellables`
29-
public class DisposableBag {
30-
31-
// MARK: Properties
32-
33-
private let lock = NSLock()
34-
private var cancellables = Set<AnyCancellable>()
35-
36-
public var count: Int {
37-
return cancellables.count
38-
}
39-
40-
// MARK: Methods
41-
42-
public func store(_ anyCancellable: AnyCancellable) {
43-
lock.lock()
44-
cancellables.insert(anyCancellable)
45-
lock.unlock()
46-
}
47-
48-
public func dispose(_ anyCancellable: AnyCancellable) {
49-
lock.lock()
50-
cancellables.remove(anyCancellable)
51-
lock.unlock()
52-
}
53-
54-
public func empty() {
55-
lock.lock()
56-
cancellables.removeAll()
57-
lock.unlock()
58-
}
59-
}
60-
6126
// MARK: - Standard Sinks
6227

6328
extension Publisher {
@@ -68,6 +33,7 @@ extension Publisher {
6833
/// - receiveError: The closure to execute on completion due to an error.
6934
/// - receiveCompletion: The closure to execute on normal completion.
7035
/// - Returns: A cancellable instance, which you use when you end assignment of the received value. Deallocation of the result will tear down the subscription stream.
36+
@_disfavoredOverload
7137
public func sink(
7238
receiveValue: @escaping (Output) -> Void,
7339
receiveError: @escaping (Failure) -> Void,
@@ -117,6 +83,7 @@ extension Publisher {
11783
/// - receiveCompletion: The closure to execute on normal completion.
11884
/// - bag: The `DisposableBag` that should store this subscriber.
11985
/// - Returns: A cancellable instance, which you use when you end assignment of the received value. Deallocation of the result will tear down the subscription stream.
86+
@_disfavoredOverload
12087
@discardableResult
12188
public func sink(
12289
receiveValue: @escaping (Output) -> Void,
@@ -196,7 +163,7 @@ private extension Subscribers.Sink {
196163
switch completion {
197164
case .failure(let error):
198165
receiveError(error)
199-
case.finished:
166+
case .finished:
200167
receiveCompletion?()
201168
}
202169
},
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
//
2+
// MIT License
3+
//
4+
// Copyright (c) 2023 Connor Ricks
5+
//
6+
// Permission is hereby granted, free of charge, to any person obtaining a copy
7+
// of this software and associated documentation files (the "Software"), to deal
8+
// in the Software without restriction, including without limitation the rights
9+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
// copies of the Software, and to permit persons to whom the Software is
11+
// furnished to do so, subject to the following conditions:
12+
//
13+
// The above copyright notice and this permission notice shall be included in all
14+
// copies or substantial portions of the Software.
15+
//
16+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
// SOFTWARE.
23+
24+
import Combine
25+
import Foundation
26+
27+
/// A thread safe bag of `AnyCancellables`
28+
public class DisposableBag {
29+
30+
// MARK: Properties
31+
32+
private let lock = NSLock()
33+
private(set) var cancellables = Set<AnyCancellable>()
34+
35+
// MARK: Methods
36+
37+
public func store(_ anyCancellable: AnyCancellable) {
38+
lock.lock()
39+
cancellables.insert(anyCancellable)
40+
lock.unlock()
41+
}
42+
43+
public func dispose(_ anyCancellable: AnyCancellable) {
44+
lock.lock()
45+
cancellables.remove(anyCancellable)
46+
lock.unlock()
47+
}
48+
49+
public func empty() {
50+
lock.lock()
51+
cancellables.removeAll()
52+
lock.unlock()
53+
}
54+
}

0 commit comments

Comments
 (0)