Skip to content

A simple swift package that provides a Swift Concurrency equivalent to `@Published`.

License

Notifications You must be signed in to change notification settings

BrentMifsud/AsyncValue

Repository files navigation

Build

AsyncValue

This is a simple package that provides a convenience property wrapper around AsyncStream that behaves almost identically to @Published.

Installation

Via Xcode

  1. In the project navigator on the left, click on your project
  2. click Package Dependencies
  3. enter: https://github.com/BrentMifsud/AsyncValue.git into the search bar
  4. 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

ObservableObject support

AsyncValue can be adapted to work seamlessly with ObservableObject with a single line of code:

class MyObservableObject: ObservableObject {
    @AsyncValue var myValue: String = "Test"
}

Any changes to an AsyncValue will trigger a view update in the same way @Published does.

Thanks to jlsiewert for coming up with the cool trick for making this work automatically.

.onReceive equivalent for AsyncSequence

There is also an .onRecieve(sequence:perform:) view modifier that allows you to respond to changes from any AsyncSequence.

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"
}

About

A simple swift package that provides a Swift Concurrency equivalent to `@Published`.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages