Skip to content

Commit fcca7a3

Browse files
authored
Added README (#1)
1 parent 52126bf commit fcca7a3

1 file changed

Lines changed: 81 additions & 0 deletions

File tree

README.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Publishable
2+
3+
![Swift](https://img.shields.io/badge/Swift-6.0-EF5239?logo=swift&labelColor=white)
4+
[![Codecov](https://codecov.io/github/NSFatalError/Publishable/graph/badge.svg?token=axMe8BnuvB)](https://codecov.io/github/NSFatalError/Publishable)
5+
6+
Synchronous observation of `Observable` changes through `Combine`
7+
8+
#### Contents
9+
- [What Problem Publishable Solves?](#what-problem-publishable-solves)
10+
- [How Publishable Works?](#how-publishable-works)
11+
- [Installation](#installation)
12+
13+
## What Problem Publishable Solves?
14+
15+
With the introduction of [SE-0475: Transactional Observation of Values](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0475-observed.md),
16+
Swift gains built-in support for observing changes to `Observable` types. This solution is great, but it only covers some of the use cases, as it
17+
publishes the updates via an `AsyncSequence`.
18+
19+
In some scenarios, however, developers need to perform actions synchronously - immediately after a change occurs.
20+
21+
This is where `Publishable` comes in. It allows `Observation` and `Combine` to coexist within a single type, letting you take advantage of the latest
22+
`Observable` features, while processing changes synchronously when needed. It even works with the `SwiftData.Model` macro!
23+
24+
```swift
25+
import Publishable
26+
27+
@Publishable @Observable
28+
final class Person {
29+
var name = "John"
30+
var surname = "Doe"
31+
32+
var fullName: String {
33+
"\(name) \(surname)"
34+
}
35+
}
36+
37+
let person = Person()
38+
let nameCancellable = person.publisher.name.sink { name in
39+
print("Name -", name)
40+
}
41+
let fullNameCancellable = person.publisher.fullName.sink { fullName in
42+
print("Full name -", fullName)
43+
}
44+
45+
// Initially prints (same as `Published` property wrapper):
46+
// Name - John
47+
// Full name - John Doe
48+
49+
person.name = "Kamil"
50+
// Prints:
51+
// Name - Kamil
52+
// Full name - Kamil Doe
53+
54+
person.surname = "Strzelecki"
55+
// Prints:
56+
// Full name - Kamil Strzelecki
57+
```
58+
59+
## How Publishable Works?
60+
61+
The `@Publishable` macro relies on two key properties of Swift Macros and `Observation` module:
62+
- Macro expansions are compiled in the context of the module where they’re used. This allows references in the macro to be overloaded by locally available symbols.
63+
- Swift exposes `ObservationRegistrar` as a documented, public API, making it possible to use it safely and directly.
64+
65+
`Publishable` leverages these facts to overload the default `ObservationRegistrar` with a custom one that:
66+
- Forwards changes to Swift’s native `ObservationRegistrar`
67+
- Simultaneously emits values through generated `Combine` publishers
68+
69+
While I acknowledge that this usage might not have been intended by the authors, I would refrain from calling it a hack.
70+
It relies solely on well-understood behaviors of Swift and its public APIs.
71+
72+
This approach has been carefully tested and verified to work with both `Observable` and `SwiftData.Model` macros.
73+
74+
## Installation
75+
76+
```swift
77+
.package(
78+
url: "https://github.com/NSFatalError/Publishable",
79+
from: "1.0.0"
80+
)
81+
```

0 commit comments

Comments
 (0)