AsyncValue
This is a simple package that provides a convenience property wrapper around AsyncStream that behaves almost identically to @Published
.
Installation
Via Xcode
- In the project navigator on the left, click on your project
- click
Package Dependencies
- enter: https://github.com/BrentMifsud/AsyncValue.git into the search bar
- select the desired version and click
Add Package
Via Package.swift
in your Package.swift
file, add the following:
let package = Package(
name: "MyPackage",
dependencies: [
.package(url: "https://github.com/BrentMifsud/AsyncValue.git", from: .init(1, 0, 0))
],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "MyPackage",
targets: ["MyPackage"]
),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "MyPackage",
dependencies: ["AsyncValue"]
),
.testTarget(
name: "MyPackageTests",
dependencies: ["MyPackage"]
),
]
)
Usage
Accessing the current value
@AsyncValue var myValue: String = "Test"
print(myValue) // prints: test
Observing changes to the value
Just like the publisher backing @Published
, AsyncValue
is backed by an AsyncStream. And you can subscribe to updates to the value.
@AsyncValue myValue: String = "Test"
Task {
for await value in myValue {
print("Value: \(value)")
}
}
Task {
await Task.sleep(nanoseconds: 1_000_000_000)
myValue = "New Value"
}
/* Prints:
Value: test
Value: New Value
*/
Observing with multiple tasks
One of the major limitations of AsyncStream out of the box is that you can only for-await-in
on it with a single task.
AsyncValue
does not have this limitation:
@AsyncValue myValue: String = "Test"
Task {
for await value in myValue {
print("Task 1 Value: \(value)")
}
}
Task {
for await value in myValue {
print("Task 2 Value: \(value)")
}
}
Task {
await Task.sleep(nanoseconds: 1_000_000_000)
myValue = "New Value"
}
/* Prints (note that the order of the tasks printing may vary as this is happening asyncronously):
Task 1 Value: test
Task 2 Value: test
Task 2 Value: New Value
Task 1 Value: New Value
*/
Using with SwiftUI
AsyncValue
can be adapted to work seamlessly with ObservableObject
with a single line of code:
class MyObservableObject: ObservableObject {
@AsyncValue var myValue: String = "Test" {
// IMPORTANT: you must use `willSet` as that is what `@Published` uses under the hood
willSet { objectWillChange.send() }
}
}
There is also an .onRecieve(stream:perform:)
view modifier that allows you to respond to changes from an @AsyncValue
struct MyView: View {
var body: some View {
Text("Hello World!")
.onReceive(myService.$myValue) { value in
print("The value changed to: \(value)")
}
}
}
class MyService: ObservableObject {
@AsyncValue var myValue: String = "Test" {
willSet { objectWillChange.send() }
}
}