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

Overview

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

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() }
    }
}
You might also like...
A command-line tool and Swift Package for generating class diagrams powered by PlantUML
A command-line tool and Swift Package for generating class diagrams powered by PlantUML

SwiftPlantUML Generate UML class diagrams from swift code with this Command Line Interface (CLI) and Swift Package. Use one or more Swift files as inp

A Swift package for rapid development using a collection of micro utility extensions for Standard Library, Foundation, and other native frameworks.
A Swift package for rapid development using a collection of micro utility extensions for Standard Library, Foundation, and other native frameworks.

ZamzamKit ZamzamKit is a Swift package for rapid development using a collection of micro utility extensions for Standard Library, Foundation, and othe

Swift implementation of the package url spec

PackageURL Swift implementation of the package url specification. Requirements Swift 5.3+ Usage import PackageURL let purl: PackageURL = "pkg:swift/a

Mechanical editing support for Package.swift manifests

Mechanical editing support for Package.swift manifests. Implements Swift Evolution proposal SE-301

Swift Package for Decoding RSS Feeds.

SyndiKit Swift Package built on top of XMLCoder for Decoding RSS Feeds. Check out the DocC-Built Site! Table of Contents Introduction Features Install

Azure Maps iOS SDK binary distribution for Swift Package Manager
Azure Maps iOS SDK binary distribution for Swift Package Manager

Azure Maps Control for iOS Installation In your Xcode iOS Project settings, under Project setting’s Package Dependencies, click on + button to add pac

Numerals is a package containing additional numeric types for the Swift programming language.

swift-numerals Numerals is a package containing additional numeric types for the Swift programming language. Contents The package currently provides t

Swift package adding fraction and percentage types.

swift-rationals Rationals is a package containing Fraction and Percentage types for the Swift programming language. Contents The package currently pro

Swift package adding measurable types.

swift-measures Measures is a package containing measurable types for the Swift programming language. Contents The package currently provides the follo

Comments
  • [Feature] automatic trigger for `objectWillChange` publisher

    [Feature] automatic trigger for `objectWillChange` publisher

    Remembering to add a willSet to all of your async value properties is not all that user friendly. I think we can do better:

    Proposed Solution:

    // this might not be needed with the new swift 5.7 generics. Will investigate. When Xcode 14 is released.
    internal protocol AnyAsyncValue {
         var projectedValue: AsyncSequence<Any>
    }
    
    internal extension AsyncValue: AnyAsyncValue {
        var projectedValue: AsyncSequence<Any> {
            projectedValue
        }
    }
    
    @MainActor open class AsyncObservableObject: ObservableObject {
        private var objectWillChangeTasks: [Task<Void, Never>] = []
        
        init() {
             setupObjectWillChangeTasks()
        }
    
        deinit {
            for task in objectWillChangeTasks {
                task.cancel()
            }
        }
    
        private func setupObjectWillChangeTasks() {
            let mirror = Mirror(reflecting: self)
    
            for child in mirror.children {
                  if let asyncValue = child.value as? AnyAsyncValue {
                      let task = Task {
                           for await _ in asyncValue.projectedValue {
                               objectWillChange.send()
                           }
                      }
    
                      objectWillChangeTasks.append(task)
                  }
            } 
        }
    }
    
    enhancement 
    opened by BrentMifsud 0
Releases(1.0.2)
  • 1.0.2(Sep 3, 2022)

    What's Changed

    • Add .onReceive(stream:perform:) view modifier and UI Tests by @BrentMifsud in https://github.com/BrentMifsud/AsyncValue/pull/4

    Full Changelog: https://github.com/BrentMifsud/AsyncValue/compare/1.0.1...1.0.2

    Source code(tar.gz)
    Source code(zip)
  • 1.0.1(Sep 2, 2022)

    What's Changed

    • Add shields to readme by @BrentMifsud in https://github.com/BrentMifsud/AsyncValue/pull/1
    • Add pull request action by @BrentMifsud in https://github.com/BrentMifsud/AsyncValue/pull/2
    • add linux support by @BrentMifsud in https://github.com/BrentMifsud/AsyncValue/pull/3

    New Contributors

    • @BrentMifsud made their first contribution in https://github.com/BrentMifsud/AsyncValue/pull/1

    Full Changelog: https://github.com/BrentMifsud/AsyncValue/compare/1.0.0...1.0.1

    Source code(tar.gz)
    Source code(zip)
  • 1.0.0(Sep 3, 2022)

    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

    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() }
        }
    }
    
    Source code(tar.gz)
    Source code(zip)
Owner
Brent Mifsud
iOS developer at @envoy
Brent Mifsud
WholesomeExtensions - A SPM package that provides some extensions that I like to use

WholesomeExtensions This package includes some extensions that I like to use. Yo

Fırat Yenidünya 1 Jan 12, 2022
BCSwiftTor - Opinionated pure Swift controller for Tor, including full support for Swift 5.5 and Swift Concurrency

BCSwiftTor Opinionated pure Swift controller for Tor, including full support for

Blockchain Commons, LLC — A “not-for-profit” benefit corporation 3 May 20, 2022
🕸️ Swift Concurrency-powered crawler engine on top of Actomaton.

??️ ActoCrawler ActoCrawler is a Swift Concurrency-powered crawler engine on top of Actomaton, with flexible customizability to create various HTML sc

Actomaton 16 May 20, 2022
Swift Package Manager plugin which runs ActionBuilder to create a Github Actions workflow for a swift package.

ActionBuilderPlugin A Swift Package Manager command which builds a Github Actions workflow for the current package. By default the workflow file will

Elegant Chaos 4 Jul 20, 2022
Angle is a simple Swift library that provides Angle structure representing angles.

Angle is a simple Swift library that provides Angle structure representing angles. It handles angles using circular measure by default but is al

Geonu Jeon 2 Nov 30, 2021
Async+ for Swift provides a simple chainable interface for your async and throwing code, similar to promises and futures

Async+ for Swift provides a simple chainable interface for your async and throwing code, similar to promises and futures. Have the best of both worlds

async_plus 114 Sep 8, 2022
A simple Swift package for counting the Syllables in a sentence.

A simple Swift package for counting the Syllables in a sentence.

null 2 Jan 3, 2022
Updeto is a simple package that help update checker for iOS Apps

Updeto is a simple package that will help you to check if the currently installed version is the same as the latest one available on App Store.

Manuel Sánchez 8 Jul 8, 2022
MiniKeePass provides secure password storage on your phone that's compatible with KeePass.

MiniKeePass MiniKeePass provides secure password storage on your phone that's compatible with KeePass. View, Edit, and Create KeePass 1.x and 2.x file

null 893 Sep 18, 2022
Swift Markdown is a Swift package for parsing, building, editing, and analyzing Markdown documents.

Swift Markdown is a Swift package for parsing, building, editing, and analyzing Markdown documents.

Apple 1.9k Sep 18, 2022