|
1 | 1 | # AsyncObservable
|
2 | 2 |
|
| 3 | +Some of the features that Combine used to offer, but using Swift concurrency and @Observable instead. So it's more compatible with modern setups and should work just fine on any platform. |
| 4 | +Designed for Swift 6. |
| 5 | + |
| 6 | + |
| 7 | +A single property that is thread safe and can be observed using async streams or @Observable. |
| 8 | + |
| 9 | +```swift |
| 10 | +import AsyncObservable |
| 11 | + |
| 12 | +actor Something { |
| 13 | + let someProperty = AsyncObservable("Hello, world!") |
| 14 | + |
| 15 | + func funcThatUpdatesProperty() async { |
| 16 | + await someProperty.update("Hello, world! 2") |
| 17 | + } |
| 18 | +} |
| 19 | + |
| 20 | +let something = Something() |
| 21 | +something.someProperty.value // "Hello, world!" |
| 22 | + |
| 23 | +for await value in something.someProperty.valueStream { |
| 24 | + print(value) // hello world (then whatever the property is updated to) |
| 25 | +} |
| 26 | + |
| 27 | + |
| 28 | +struct SomethingView: View { |
| 29 | + let something: Something // Note: someProperty should be marked with @MainActor for this to work as is |
| 30 | + var body: some View { |
| 31 | + Text(something.someProperty.valueObservable) // hello world (then whatever the property is updated to) |
| 32 | + } |
| 33 | +} |
| 34 | +``` |
| 35 | + |
| 36 | + |
| 37 | +## Stream |
| 38 | + |
| 39 | +The streams buffering policy defaults to `.unbounded`, so it will "gather" values as soon as you create it. |
| 40 | + |
| 41 | +```swift |
| 42 | +let someProperty = AsyncObservable(1) |
| 43 | + |
| 44 | +let stream = someProperty.valueStream // already has 1 |
| 45 | +someProperty.update { $0 + 1 } // 2 |
| 46 | +someProperty.update { $0 + 1 } // 3 |
| 47 | +someProperty.update { $0 + 1 } // 4 |
| 48 | + |
| 49 | +for await value in stream { |
| 50 | + print(value) // 1, 2, 3, 4 |
| 51 | +} |
| 52 | +``` |
| 53 | + |
| 54 | +Canceling the task that the stream is running in will cancel the stream. So you don't need to have manual `if Task.isCancelled` checks. But you can still check it if you want. |
| 55 | + |
| 56 | +```swift |
| 57 | +let someProperty = AsyncObservable(1) |
| 58 | + |
| 59 | +let stream = someProperty.valueStream // already has 1 |
| 60 | +let task = Task { |
| 61 | + for await value in stream { |
| 62 | + print(value) // 1, 2, 3 |
| 63 | + } |
| 64 | +} |
| 65 | + |
| 66 | + |
| 67 | +someProperty.update { $0 + 1 } // 2 |
| 68 | +someProperty.update { $0 + 1 } // 3 |
| 69 | +task.cancel() |
| 70 | +someProperty.update { $0 + 1 } // 4 |
| 71 | +``` |
| 72 | + |
| 73 | +Streams are finalized as soon as you break out of the loop, so you can't reuse them. But you can create as many new ones as you like. |
| 74 | + |
| 75 | +```swift |
| 76 | +let someProperty = AsyncObservable(1) |
| 77 | + |
| 78 | +let stream = someProperty.valueStream // already has 1 |
| 79 | +// only print first value |
| 80 | +for await value in stream { |
| 81 | + print(value) // 1 |
| 82 | + break |
| 83 | +} |
| 84 | + |
| 85 | +// don't do this ❌ |
| 86 | +// the stream is already finalized |
| 87 | +for await value in stream { |
| 88 | +} |
| 89 | + |
| 90 | +// do this ✅ |
| 91 | +for await value in someProperty.valueStream { |
| 92 | + |
| 93 | +} |
| 94 | +``` |
| 95 | + |
| 96 | +## Mutate |
| 97 | + |
| 98 | +Sometimes you just want to mutate the original value instead of having to copy and return a new value. This still updates all the observers correctly and is safe. |
| 99 | + |
| 100 | +```swift |
| 101 | +let values = AsyncObservable([1, 2, 3]) |
| 102 | + |
| 103 | +values.mutate { $0.append(4) } |
| 104 | +``` |
| 105 | + |
| 106 | + |
| 107 | +## Buffering Policy |
| 108 | + |
| 109 | +The buffering policy defaults to `.unbounded`, but you can change it on init. |
| 110 | + |
| 111 | +```swift |
| 112 | +let someProperty = AsyncObservable("Hello, world!", bufferingPolicy: .bufferingNewest(1)) |
| 113 | +``` |
| 114 | + |
| 115 | +## DispatchQueue |
| 116 | + |
| 117 | +You can pass a custom dispatch queue to the initializer, just make sure it's a serial queue. Don't change the queue unless you really need to. |
| 118 | + |
| 119 | +```swift |
| 120 | +let someProperty = AsyncObservable("Hello, world!", dispatchQueue: DispatchSerialQueue(label: "SomeQueue")) |
| 121 | +``` |
| 122 | + |
| 123 | +## UserDefaults |
| 124 | + |
| 125 | +Use the `AsyncObservableUserDefaults` class to store values in UserDefaults. Works just the same as `AsyncObservable`, but automatically saves to UserDefaults and loads from there. |
| 126 | + |
| 127 | +```swift |
| 128 | +let someProperty = AsyncObservableUserDefaults("someKey", initialValue: "Hello, world!") |
| 129 | +``` |
| 130 | + |
0 commit comments