Skip to content

Commit 8c01bdf

Browse files
committed
first commit
0 parents  commit 8c01bdf

File tree

6 files changed

+397
-0
lines changed

6 files changed

+397
-0
lines changed

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
xcuserdata/
5+
DerivedData/
6+
.swiftpm/configuration/registries.json
7+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8+
.netrc
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>IDEDidComputeMac32BitWarning</key>
6+
<true/>
7+
</dict>
8+
</plist>

Package.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// swift-tools-version: 5.10
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "Bark",
8+
products: [
9+
// Products define the executables and libraries a package produces, making them visible to other packages.
10+
.library(
11+
name: "Bark",
12+
targets: ["Bark"]),
13+
],
14+
targets: [
15+
// Targets are the basic building blocks of a package, defining a module or a test suite.
16+
// Targets can depend on other targets in this package and products from dependencies.
17+
.target(
18+
name: "Bark"),
19+
.testTarget(
20+
name: "BarkTests",
21+
dependencies: ["Bark"]),
22+
]
23+
)

README.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Bark
2+
Simple message broadcasting library (similar to NotificationCenter in spirit). It's designed to help you manage a group of dependencies in a container, and easily resolve them when needed, thus helping make your project more modular, testable, and maintainable.
3+
4+
Tree bark won't bark. But `bark` will, and it will allow you to broadcast information across your app in a structured concurrent way.
5+
6+
## Why Bark
7+
With `bark`, you can register subscriptions that require a concurrency context, and be assured at the point of use that the subscription was
8+
run when the `await` to the associated post completes.
9+
10+
This allows you to reason about the order in which tasks associated with subscriptions execute. Making it easier to write testable and understandable code.
11+
12+
## Features
13+
- **Lightweight and Focused** - Specifically responsible for dealing with message broadcasting.
14+
- **Swift-native Design** - Bark feels natural and intuitive for Swift developers.
15+
- **Thread-safe**
16+
- **Simple** - Very simple syntax
17+
18+
## Installation
19+
Bark is available through the Swift Package Manager. To install it, simply add the following line to your `Package.swift` file:
20+
21+
```swift
22+
dependencies: [
23+
.package(url: "https://github.com/willowtreeapps/bark.git", from: "1.0.0")
24+
]
25+
```
26+
27+
## Usage
28+
Bark allows for structured concurrent message broadcasting. Here's an example from its tests:
29+
30+
### Registration
31+
32+
```swift
33+
func testPostsOfASingleSubscription() async throws {
34+
// Given
35+
let subscriptions = Bark.Store()
36+
var testNotification1PostCount = 0
37+
38+
func increaseTheCounter() async {
39+
testNotification1PostCount += 1
40+
}
41+
42+
bark.subscribe(.testNotification1, in: subscriptions) { _ in
43+
await increaseTheCounter()
44+
}
45+
46+
// When
47+
await bark.post(.testNotification1)
48+
await bark.post(.testNotification1)
49+
50+
// Then
51+
XCTAssertEqual(testNotification1PostCount, 2)
52+
}
53+
```
54+
55+
## Contributing
56+
Contributions are immensely appreciated. Feel free to submit pull requests or to create issues to discuss any potential bugs or improvements.
57+
58+
## Author
59+
Grove was created by @rafcabezas at [WillowTree, Inc](https://willowtreeapps.com).
60+
61+
## License
62+
Grove is available under the [MIT license](https://opensource.org/licenses/MIT).

Sources/Bark/Bark.swift

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
//
2+
// Bark.swift
3+
//
4+
//
5+
// Created by Raf Cabezas on 6/6/24.
6+
//
7+
8+
import Foundation
9+
10+
public final class Bark: @unchecked Sendable {
11+
12+
// MARK: - Types
13+
14+
public typealias Block = (Any?) async -> Void
15+
16+
public struct Name: Hashable, Equatable {
17+
private let value: String
18+
public init(_ value: String) {
19+
self.value = value
20+
}
21+
22+
public var description: String { value }
23+
}
24+
25+
public final class Store: @unchecked Sendable {
26+
struct Handler {
27+
let name: Name
28+
let block: Block
29+
}
30+
31+
fileprivate let id = UUID()
32+
fileprivate var handlers: [Handler] = []
33+
private let lock = NSLock()
34+
35+
public init() {}
36+
37+
func add(_ block: @escaping Block, for name: Name) {
38+
lock.lock()
39+
handlers.append(Handler(name: name, block: block))
40+
lock.unlock()
41+
}
42+
43+
func blocks(for name: Name) -> [Block] {
44+
lock.lock()
45+
let blocks = handlers.compactMap { $0.name == name ? $0.block : nil }
46+
lock.unlock()
47+
return blocks
48+
}
49+
50+
func clear() {
51+
lock.lock()
52+
handlers.removeAll()
53+
lock.unlock()
54+
}
55+
}
56+
57+
// MARK: - Properties
58+
59+
private let lock = NSLock()
60+
private struct StoreWrapper {
61+
weak var store: Store?
62+
}
63+
private var stores = [StoreWrapper]()
64+
65+
// MARK: - Lifecycle
66+
67+
public init() {
68+
/* No-Op */
69+
}
70+
71+
// MARK: - Actions
72+
73+
public func subscribe(_ name: Name, in store: Store?, block: @escaping Block) {
74+
guard let store else { return }
75+
lock.lock()
76+
store.add(block, for: name)
77+
if !stores.contains(where: { $0.store?.id == store.id }) {
78+
stores.append(StoreWrapper(store: store))
79+
}
80+
lock.unlock()
81+
}
82+
83+
public func unsubscribe(_ store: Store?) {
84+
guard let store else { return }
85+
lock.lock()
86+
store.clear()
87+
stores.removeAll { $0.store?.id == store.id }
88+
lock.unlock()
89+
}
90+
91+
public func post(_ name: Name, data: Any? = nil) async {
92+
for block in blocks(for: name) {
93+
await block(data)
94+
}
95+
}
96+
97+
// MARK: - Helpers
98+
99+
public func registrationsCount(for name: Name) -> Int {
100+
blocks(for: name).count
101+
}
102+
103+
private func blocks(for name: Name) -> [Block] {
104+
lock.lock()
105+
let blocks = stores
106+
.compactMap { $0.store?.blocks(for: name) }
107+
.flatMap { $0 }
108+
lock.unlock()
109+
return blocks
110+
}
111+
}

0 commit comments

Comments
 (0)