Skip to content

Commit 5a2a704

Browse files
Add Shared.read (#138)
* map * naming * Apply suggestions from code review --------- Co-authored-by: Stephen Celis <[email protected]>
1 parent 732871f commit 5a2a704

File tree

5 files changed

+103
-0
lines changed

5 files changed

+103
-0
lines changed

Sources/Sharing/Internal/Reference.swift

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,56 @@ where Base: MutableReference, Path: WritableKeyPath<Base.Value, Value> {
617617
}
618618
}
619619

620+
final class _ReadClosureReference<Base: Reference, Value>:
621+
Reference,
622+
Observable
623+
{
624+
private let base: Base
625+
private let body: @Sendable (Base.Value) -> Value
626+
627+
init(base: Base, body: @escaping @Sendable (Base.Value) -> Value) {
628+
self.base = base
629+
self.body = body
630+
}
631+
632+
var id: ObjectIdentifier {
633+
base.id
634+
}
635+
636+
var isLoading: Bool {
637+
base.isLoading
638+
}
639+
640+
var loadError: (any Error)? {
641+
base.loadError
642+
}
643+
644+
var wrappedValue: Value {
645+
body(base.wrappedValue)
646+
}
647+
648+
func load() async throws {
649+
try await base.load()
650+
}
651+
652+
func touch() {
653+
base.touch()
654+
}
655+
656+
#if canImport(Combine)
657+
var publisher: any Publisher<Value, Never> {
658+
func open(_ publisher: some Publisher<Base.Value, Never>) -> any Publisher<Value, Never> {
659+
publisher.map(body)
660+
}
661+
return open(base.publisher)
662+
}
663+
#endif
664+
665+
var description: String {
666+
".map(\(base.description), as: \(Value.self).self)"
667+
}
668+
}
669+
620670
final class _OptionalReference<Base: Reference<Value?>, Value>:
621671
Reference,
622672
Observable,

Sources/Sharing/Shared.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,15 @@ public struct Shared<Value> {
223223
reference = newValue.reference
224224
}
225225
}
226+
227+
/// Returns a read-only shared reference to the resulting value of a given closure.
228+
///
229+
/// - Returns: A new read-only shared reference.
230+
public func read<Member>(
231+
_ body: @escaping @Sendable(Value) -> Member
232+
) -> SharedReader<Member> {
233+
SharedReader(self).read(body)
234+
}
226235

227236
/// Returns a shared reference to the resulting value of a given key path.
228237
///

Sources/Sharing/SharedReader.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,20 @@ public struct SharedReader<Value> {
155155
reference = newValue.reference
156156
}
157157
}
158+
159+
/// Returns a read-only shared reference to the resulting value of a given closure.
160+
///
161+
/// - Returns: A new shared reader.
162+
public func read<Member>(
163+
_ body: @escaping @Sendable (Value) -> Member
164+
) -> SharedReader<Member> {
165+
func open(_ reference: some Reference<Value>) -> SharedReader<Member> {
166+
SharedReader<Member>(
167+
reference: _ReadClosureReference(base: reference, body: body)
168+
)
169+
}
170+
return open(reference)
171+
}
158172

159173
/// Returns a read-only shared reference to the resulting value of a given key path.
160174
///

Tests/SharingTests/EquatableTests.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,18 @@ struct EquatableTests {
3232
#expect(lhs == rhs)
3333
#expect($lhs == $rhs)
3434
}
35+
36+
@Test func mapReader() {
37+
@Shared(value: 0) var base: Int
38+
@SharedReader var lhs: Int
39+
@SharedReader var rhs: Int
40+
_lhs = $base.read { $0 * 2 }
41+
_rhs = $base.read { $0 * 3 }
42+
#expect(lhs == rhs)
43+
#expect($lhs == $rhs)
44+
45+
$base.withLock { $0 += 1 }
46+
#expect(lhs != rhs)
47+
#expect($lhs != $rhs)
48+
}
3549
}

Tests/SharingTests/SharedTests.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,22 @@ import Testing
8787

8888
#expect(id == 42)
8989
}
90+
91+
@Test func mapReader() {
92+
@Shared(value: 0) var count
93+
@SharedReader var isZero: Bool
94+
_isZero = $count.read { $0 == 0 }
95+
96+
#expect(isZero)
97+
98+
$count.withLock { $0 += 1 }
99+
100+
#expect(!isZero)
101+
102+
$count = Shared(value: 0)
103+
104+
#expect(!isZero)
105+
}
90106

91107
@Test func optional() throws {
92108
@Shared(value: nil) var wrappedCount: Int?

0 commit comments

Comments
 (0)