From 656961fb0b54bcf3d6eda70b38f3cf1e813631a6 Mon Sep 17 00:00:00 2001 From: Zach Date: Thu, 8 Aug 2024 17:30:57 -0600 Subject: [PATCH 01/13] Update README.md --- README.md | 249 ++++++------------------------------------------------ 1 file changed, 26 insertions(+), 223 deletions(-) diff --git a/README.md b/README.md index 9d2ad2d..a4c8e34 100644 --- a/README.md +++ b/README.md @@ -1,236 +1,39 @@ # Later -`Later` is a lightweight Swift 6 library designed to simplify asynchronous programming by providing foundational building blocks such as `SendableValue`, `Future`, `Deferred`, `Stream`, and `Publisher`. These components enable you to manage and coordinate asynchronous tasks, making it easier to write clean and maintainable code. +**Later** is a Swift 6 library that simplifies asynchronous programming by offering simple building blocks for managing concurrency. **Later** helps you write clean, maintainable code that efficiently handles complex asynchronous tasks. -## Features +## Key Features -- **SendableValue**: A generic [`Sendable`](https://developer.apple.com/documentation/swift/sendable) value that uses [`OSAllocatedUnfairLock`](https://developer.apple.com/documentation/os/osallocatedunfairlock). -- **Future**: Represents a value that will be available asynchronously in the future. -- **Deferred**: Represents a value that will be computed and available asynchronously when explicitly started. -- **Stream**: Represents an asynchronous sequence of values emitted over time. -- **Publisher**: Allows objects to subscribe to changes in state or data and notifies subscribers when the state or data changes. -- **Subscribing**: A protocol for objects that want to observe changes in state or data. +**Later** offers a range of tools to make asynchronous programming more straightforward and efficient: -## Installation +- **SendableValue**: A generic [`Sendable`](https://developer.apple.com/documentation/swift/sendable) value that ensures thread safety using [`OSAllocatedUnfairLock`](https://developer.apple.com/documentation/os/osallocatedunfairlock). +- **Future**: Represents a value that will be available asynchronously in the future, enabling you to handle tasks that take time to complete. +- **Deferred**: Represents a value that will be computed and available asynchronously when explicitly started, giving you control over when a task begins. +- **Stream**: Represents an asynchronous sequence of values emitted over time, perfect for handling data that updates periodically. +- **Publisher**: Allows objects to subscribe to changes in state or data, notifying subscribers when updates occur, ensuring your application responds dynamically to changes. +- **Subscribing**: A protocol for objects that want to observe changes in state or data, making it easy to react to updates. -### Swift Package Manager +## Getting Started -Add `Later` to your `Package.swift` file: +To start using **Later**, follow our [Installation Guide](01-installation.md) which provides step-by-step instructions for adding **Later** to your Swift project using Swift Package Manager. -```swift -dependencies: [ - .package(url: "https://github.com/0xLeif/Later.git", from: "1.0.0") -] -``` +After installation, explore our [Usage Overview](03-usage-overview.md) to see how to implement each of the key features in your own code. From simple examples to more in-depth explorations, these guides will help you integrate **Later** into your asynchronous workflows effectively. -And add it to your target’s dependencies: +## Documentation -```swift -.target( - name: "YourTarget", - dependencies: ["Later"] -) -``` +Here’s a breakdown of the **Later** documentation: -## Usage +- [Installation Guide](01-installation.md): Instructions on how to install **Later** using Swift Package Manager. +- [Usage Overview](03-usage-overview.md): An overview of **Later**'s key features with example implementations. +- Detailed Usage Guides: + - [SendableValue Usage](usage-sendablevalue.md) + - [Future Usage](usage-future.md) + - [Deferred Usage](usage-deferred.md) + - [Stream Usage](usage-stream.md) + - [Publisher and Subscribing Usage](usage-publisher.md) + - [Schedule Task Usage](usage-schedule-task.md) +- [Contributing](contributing.md): Information on how to contribute to the **Later** project. -### SendableValue +## Next Steps -SendableValue is a thread-safe value-type wrapper for a value that can be safely shared across concurrent tasks. It allows you to set and retrieve the value asynchronously. - -```swift -let sendableValue = SendableValue(42) -sendableValue.set(value: 100) -let value = await sendableValue.value -#expect(value == 100) -``` - -This ensures that the value is safely managed across different contexts, providing a simple way to handle mutable state in concurrent programming. - -### Future - -A `Future` represents a value that will be available asynchronously in the future. - -```swift -import Later - -@Sendable func asyncTask() async throws -> String { - return "Hello" -} - -let future = Future { - do { - let result = try await asyncTask() - return result - } catch { - throw error - } -} - -do { - let result = try await future.value - print(result) // Prints "Hello" -} catch { - print("Error: \(error)") -} -``` - -### Deferred - -A `Deferred` represents a value that will be computed and available asynchronously when explicitly started. - -```swift -import Later - -@Sendable func asyncDeferredTask() async throws -> String { - return "Deferred Hello" -} - -var deferred = Deferred { - do { - let result = try await asyncDeferredTask() - return result - } catch { - throw error - } -} - -deferred.start() - -do { - let result = try await deferred.value - print(result) // Prints "Deferred Hello" -} catch { - print("Error: \(error)") -} -``` - -### Stream - -A `Stream` represents an asynchronous sequence of values emitted over time. - -```swift -import Later - -@Sendable func asyncStreamTask1() async throws -> String { - return "First value" -} - -@Sendable func asyncStreamTask2() async throws -> String { - return "Second value" -} - -let stream = Stream { emitter in - do { - let value1 = try await asyncStreamTask1() - emitter.emit(value: value1) - let value2 = try await asyncStreamTask2() - emitter.emit(value: value2) - } catch { - // Handle error if necessary - } -} - -Task { - for try await value in stream { - print(value) // Prints "First value" and then "Second value" - } -} -``` - -### Publisher and Subscribing - -A `Publisher` allows objects to subscribe to changes in data and notifies subscribers when the data changes. - -```swift -import Later - -class MySubscriber: Subscribing { - typealias Value = String - - func didUpdate(newValue: String?) { - print("New value: \(String(describing: newValue))") - } -} - -let subscriber = MySubscriber() -let publisher = Publisher(initialValue: "Initial value", subscribers: [subscriber]) - -// Using Future with Publisher -let future = await publisher.future( - didSucceed: nil, - didFail: nil, - task: { - return "Future value" - } -) - -do { - let value = try await future.value - print("Future completed with value: \(value)") -} catch { - print("Future failed with error: \(error)") -} - -// Using Deferred with Publisher -var deferred = await publisher.deferred( - didSucceed: nil, - didFail: nil, - task: { - return "Deferred value" - } -) - -deferred.start() - -do { - let value = try await deferred.value - print("Deferred completed with value: \(value)") -} catch { - print("Deferred failed with error: \(error)") -} - -// Using Stream with Publisher -let stream = await publisher.stream( - didSucceed: nil, - didFail: nil, - task: { emitter in - emitter.emit(value: "Stream value 1") - emitter.emit(value: "Stream value 2") - } -) - -var streamValues: [String] = [] -for try await value in stream { - streamValues.append(value) - print("Stream emitted value: \(value)") -} - -print("Stream completed with values: \(streamValues)") -``` - -### Schedule Task - -You can schedule tasks to be executed after a specified duration using the `Task` extension. - -Availability: `@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)` - -```swift -import Later - -func asyncScheduledTask() async throws { - print("Task executed") -} - -do { - try await Task.schedule(for: .seconds(5)) { - try await asyncScheduledTask() - } -} catch { - print("Failed to execute task: \(error)") -} -``` - -## Contributing - -Contributions are welcome! Please feel free to submit a pull request or open an issue if you have any suggestions or bug reports. Please create an issue before submitting any pull request to make sure the work isn’t already being worked on by someone else. +To continue, head over to our [Installation Guide](01-installation.md) and get **Later** set up in your project. After installation, you can dive into the [Usage Overview](03-usage-overview.md) to see how to start leveraging the power of asynchronous programming with **Later**. From 584be12705cd58b99d7b9dc628fe536aba82023f Mon Sep 17 00:00:00 2001 From: Zach Date: Thu, 8 Aug 2024 17:36:10 -0600 Subject: [PATCH 02/13] Create installation.md --- documentation/installation.md | 84 +++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 documentation/installation.md diff --git a/documentation/installation.md b/documentation/installation.md new file mode 100644 index 0000000..6406640 --- /dev/null +++ b/documentation/installation.md @@ -0,0 +1,84 @@ +# Installation Guide + +This guide will walk you through the process of installing **Later** into your Swift project using Swift Package Manager. + +## Swift Package Manager + +**Later** can be easily integrated into your project using Swift Package Manager. Follow the steps below to add **Later** as a dependency. + +### Step 1: Update Your `Package.swift` File + +Add **Later** to the `dependencies` section of your `Package.swift` file: + +```swift +dependencies: [ + .package(url: "https://github.com/0xLeif/Later.git", from: "1.0.0") +] +``` + +### Step 2: Add Later to Your Target + +Include Later in your target’s dependencies: + +```swift +.target( + name: "YourTarget", + dependencies: ["Later"] +) +``` + +### Step 3: Build Your Project + +Once you’ve added Later to your Package.swift file, build your project to fetch the dependency and integrate it into your codebase. + +``` +swift build +``` + +### Step 4: Import Later in Your Code + +Now, you can start using Later in your project by importing it at the top of your Swift files: + +```swift +import Later +``` + +## Xcode + +If you prefer to add **Later** directly through Xcode, follow these steps: + +### Step 1: Open Your Xcode Project + +Open your Xcode project or workspace. + +### Step 2: Add a Swift Package Dependency + +1. Navigate to the project navigator and select your project file. +2. In the project editor, select your target, and then go to the "Swift Packages" tab. +3. Click the "+" button to add a package dependency. + +### Step 3: Enter the Repository URL + +In the "Choose Package Repository" dialog, enter the following URL: `https://github.com/0xLeif/Later.git` + +Then click "Next." + +### Step 4: Specify the Version + +Choose the version you wish to use. It's recommended to select the "Up to Next Major Version" option and specify `1.0.0` as the lower bound. Then click "Next." + +### Step 5: Add the Package + +Xcode will fetch the package and present you with options to add **Later** to your target. Make sure to select the correct target and click "Finish." + +### Step 6: Import `Later` in Your Code + +You can now import **Later** at the top of your Swift files: + +```swift +import Later +``` + +## Next Steps + +With Later installed, you can move on to the [Usage Overview](usage-overview.md) to see how to implement the key features in your project. From 11665d3654b0e6cdd4ee0dca3ee81a27a0b1fdde Mon Sep 17 00:00:00 2001 From: Zach Date: Thu, 8 Aug 2024 17:36:54 -0600 Subject: [PATCH 03/13] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a4c8e34..0cec494 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ ## Getting Started -To start using **Later**, follow our [Installation Guide](01-installation.md) which provides step-by-step instructions for adding **Later** to your Swift project using Swift Package Manager. +To start using **Later**, follow our [Installation Guide](documentation/installation.md) which provides step-by-step instructions for adding **Later** to your Swift project using Swift Package Manager. After installation, explore our [Usage Overview](03-usage-overview.md) to see how to implement each of the key features in your own code. From simple examples to more in-depth explorations, these guides will help you integrate **Later** into your asynchronous workflows effectively. From deba5724236ab7a26fe167f7f59556a762d1bf98 Mon Sep 17 00:00:00 2001 From: Zach Date: Thu, 8 Aug 2024 17:40:19 -0600 Subject: [PATCH 04/13] Create usage-overview.md --- documentation/usage-overview.md | 236 ++++++++++++++++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 documentation/usage-overview.md diff --git a/documentation/usage-overview.md b/documentation/usage-overview.md new file mode 100644 index 0000000..a96b4b5 --- /dev/null +++ b/documentation/usage-overview.md @@ -0,0 +1,236 @@ + +# Usage Overview + +This overview provides a quick introduction to using the key components of the **Later** library. Each section includes simple examples to help you get started with **Later** in your Swift projects. + +## SendableValue + +`SendableValue` is a thread-safe wrapper for a value that can be safely shared across concurrent tasks. It allows you to set and retrieve the value asynchronously. + +### Example + +```swift +import Later + +let sendableValue = SendableValue(42) +sendableValue.set(value: 100) +let value = await sendableValue.value +#expect(value == 100) +``` + +This example shows how to use `SendableValue` to safely manage a mutable state across different contexts in concurrent programming. + +## Future + +A `Future` represents a value that will be available asynchronously in the future. It allows you to work with values that are not immediately available. + +### Example + +```swift +import Later + +@Sendable func asyncTask() async throws -> String { + return "Hello" +} + +let future = Future { + do { + let result = try await asyncTask() + return result + } catch { + throw error + } +} + +do { + let result = try await future.value + print(result) // Prints "Hello" +} catch { + print("Error: \(error)") +} +``` + +This example demonstrates how to create and retrieve a value from a `Future`, which will be available asynchronously. + +## Deferred + +`Deferred` is similar to `Future` but requires explicit start. It represents a value that will be computed and available asynchronously when explicitly started. + +### Example + +```swift +import Later + +@Sendable func asyncDeferredTask() async throws -> String { + return "Deferred Hello" +} + +var deferred = Deferred { + do { + let result = try await asyncDeferredTask() + return result + } catch { + throw error + } +} + +deferred.start() + +do { + let result = try await deferred.value + print(result) // Prints "Deferred Hello" +} catch { + print("Error: \(error)") +} +``` + +This example shows how to work with `Deferred`, where the task begins only when you explicitly call `start()`. + +## Stream + +A `Stream` represents an asynchronous sequence of values emitted over time. It’s useful for handling data that updates periodically. + +### Example + +```swift +import Later + +@Sendable func asyncStreamTask1() async throws -> String { + return "First value" +} + +@Sendable func asyncStreamTask2() async throws -> String { + return "Second value" +} + +let stream = Stream { emitter in + do { + let value1 = try await asyncStreamTask1() + emitter.emit(value: value1) + let value2 = try await asyncStreamTask2() + emitter.emit(value: value2) + } catch { + // Handle error if necessary + } +} + +Task { + for try await value in stream { + print(value) // Prints "First value" and then "Second value" + } +} +``` + +This example demonstrates how to use `Stream` to handle sequences of values emitted asynchronously over time. + +## Publisher and Subscribing + +`Publisher` allows objects to subscribe to changes in data, notifying subscribers when updates occur. This is useful for building reactive systems. + +### Example + +```swift +import Later + +class MySubscriber: Subscribing { + typealias Value = String + + func didUpdate(newValue: String?) { + print("New value: \(String(describing: newValue))") + } +} + +let subscriber = MySubscriber() +let publisher = Publisher(initialValue: "Initial value", subscribers: [subscriber]) + +// Using Future with Publisher +let future = await publisher.future( + didSucceed: nil, + didFail: nil, + task: { + return "Future value" + } +) + +do { + let value = try await future.value + print("Future completed with value: \(value)") +} catch { + print("Future failed with error: \(error)") +} + +// Using Deferred with Publisher +var deferred = await publisher.deferred( + didSucceed: nil, + didFail: nil, + task: { + return "Deferred value" + } +) + +deferred.start() + +do { + let value = try await deferred.value + print("Deferred completed with value: \(value)") +} catch { + print("Deferred failed with error: \(error)") +} + +// Using Stream with Publisher +let stream = await publisher.stream( + didSucceed: nil, + didFail: nil, + task: { emitter in + emitter.emit(value: "Stream value 1") + emitter.emit(value: "Stream value 2") + } +) + +var streamValues: [String] = [] +for try await value in stream { + streamValues.append(value) + print("Stream emitted value: \(value)") +} + +print("Stream completed with values: \(streamValues)") +``` + +This example illustrates how to use `Publisher` and `Subscribing` to create reactive systems that respond to data changes. + +## Schedule Task + +**Later** also provides the ability to schedule tasks to be executed after a specified duration using the `Task` extension. + +### Example + +```swift +import Later + +func asyncScheduledTask() async throws { + print("Task executed") +} + +do { + try await Task.schedule(for: .seconds(5)) { + try await asyncScheduledTask() + } +} catch { + print("Failed to execute task: \(error)") +} +``` + +This example shows how to schedule a task to run after a delay. + +## Next Steps + +For more in-depth details on each component, refer to the specific usage guides: + +- [SendableValue Usage](usage-sendablevalue.md) +- [Future Usage](usage-future.md) +- [Deferred Usage](usage-deferred.md) +- [Stream Usage](usage-stream.md) +- [Publisher and Subscribing Usage](usage-publisher.md) +- [Schedule Task Usage](usage-schedule-task.md) + +These guides will help you explore each feature of **Later** in more detail and provide additional examples. From 7be6c029c3817496d91fc6bcf691ca8e9aeaeb04 Mon Sep 17 00:00:00 2001 From: Zach Date: Thu, 8 Aug 2024 17:41:21 -0600 Subject: [PATCH 05/13] Update README.md --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 0cec494..9c7413e 100644 --- a/README.md +++ b/README.md @@ -17,23 +17,23 @@ To start using **Later**, follow our [Installation Guide](documentation/installation.md) which provides step-by-step instructions for adding **Later** to your Swift project using Swift Package Manager. -After installation, explore our [Usage Overview](03-usage-overview.md) to see how to implement each of the key features in your own code. From simple examples to more in-depth explorations, these guides will help you integrate **Later** into your asynchronous workflows effectively. +After installation, explore our [Usage Overview](documentation/usage-overview.md) to see how to implement each of the key features in your own code. From simple examples to more in-depth explorations, these guides will help you integrate **Later** into your asynchronous workflows effectively. ## Documentation Here’s a breakdown of the **Later** documentation: -- [Installation Guide](01-installation.md): Instructions on how to install **Later** using Swift Package Manager. -- [Usage Overview](03-usage-overview.md): An overview of **Later**'s key features with example implementations. +- [Installation Guide](documentation/installation.md): Instructions on how to install **Later** using Swift Package Manager. +- [Usage Overview](documentation/usage-overview.md): An overview of **Later**'s key features with example implementations. - Detailed Usage Guides: - - [SendableValue Usage](usage-sendablevalue.md) - - [Future Usage](usage-future.md) - - [Deferred Usage](usage-deferred.md) - - [Stream Usage](usage-stream.md) - - [Publisher and Subscribing Usage](usage-publisher.md) - - [Schedule Task Usage](usage-schedule-task.md) -- [Contributing](contributing.md): Information on how to contribute to the **Later** project. + - [SendableValue Usage](documentation/usage-sendablevalue.md) + - [Future Usage](documentation/usage-future.md) + - [Deferred Usage](documentation/usage-deferred.md) + - [Stream Usage](documentation/usage-stream.md) + - [Publisher and Subscribing Usage](documentation/usage-publisher.md) + - [Schedule Task Usage](documentation/usage-schedule-task.md) +- [Contributing](documentation/contributing.md): Information on how to contribute to the **Later** project. ## Next Steps -To continue, head over to our [Installation Guide](01-installation.md) and get **Later** set up in your project. After installation, you can dive into the [Usage Overview](03-usage-overview.md) to see how to start leveraging the power of asynchronous programming with **Later**. +To continue, head over to our [Installation Guide](documentation/installation.md) and get **Later** set up in your project. After installation, you can dive into the [Usage Overview](documentation/usage-overview.md) to see how to start leveraging the power of asynchronous programming with **Later**. From c80fc549a13a26f57cf68461db148e72829aaffb Mon Sep 17 00:00:00 2001 From: Zach Date: Thu, 8 Aug 2024 17:45:53 -0600 Subject: [PATCH 06/13] Create usage-sendablevalue.md --- documentation/usage-sendablevalue.md | 71 ++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 documentation/usage-sendablevalue.md diff --git a/documentation/usage-sendablevalue.md b/documentation/usage-sendablevalue.md new file mode 100644 index 0000000..213d52a --- /dev/null +++ b/documentation/usage-sendablevalue.md @@ -0,0 +1,71 @@ + +# SendableValue Usage + +`SendableValue` is a powerful tool provided by **Later** that allows you to manage thread-safe, mutable values across concurrent tasks in a Swift project. It wraps a value that can be safely shared between different contexts, ensuring that the value is handled properly even when accessed by multiple tasks simultaneously. + +## Overview + +The `SendableValue` type ensures that the value it holds is safely accessible in a concurrent environment. It uses [`OSAllocatedUnfairLock`](https://developer.apple.com/documentation/os/osallocatedunfairlock) to manage locking, making it efficient and secure for multi-threaded operations. + +## Example Usage + +Here’s how to use `SendableValue` in a typical scenario: + +### Creating and Setting a Value + +You can create a `SendableValue` by initializing it with a value. You can then set the value asynchronously using the `set(value:)` method. + +```swift +import Later + +let sendableValue = SendableValue(42) +sendableValue.set(value: 100) +``` + +### Accessing the Value Asynchronously + +The value stored in `SendableValue` can be accessed asynchronously using the `value` property. + +```swift +let value = await sendableValue.value +print(value) // Prints "100" +``` + +### Ensuring Thread Safety + +Because `SendableValue` is designed to be thread-safe, you can confidently use it in environments where multiple tasks might attempt to read or write the value simultaneously. + +### Full Example + +Here’s a complete example that demonstrates creating a `SendableValue`, updating it, and accessing it from an asynchronous context: + +```swift +import Later + +let sendableValue = SendableValue(42) + +// Update the value asynchronously +Task { + sendableValue.set(value: 100) +} + +// Access the value asynchronously +Task { + let value = await sendableValue.value + print(value) // Prints "100" +} +``` + +In this example, the value is set and then accessed in separate asynchronous tasks, showcasing the thread safety and ease of use provided by `SendableValue`. + +## Best Practices + +- **Use in Concurrent Environments**: Utilize `SendableValue` whenever you need to share and mutate values across multiple concurrent tasks. +- **Avoid Excessive Locking**: Although `SendableValue` handles locking internally, be mindful of performance when frequently updating the value in a highly concurrent environment. + +## Conclusion + +`SendableValue` is an essential component of the **Later** library that provides a simple, safe way to manage mutable state in asynchronous Swift programming. By using `SendableValue`, you can ensure that your values are handled correctly across concurrent tasks, making your code more robust and maintainable. + +For more advanced usage, explore other components of the **Later** library like [Future](usage-future.md), [Deferred](usage-deferred.md), and [Stream](usage-stream.md). + From d38b8fd2c48b40dc47e59a2279aa99daac78ade8 Mon Sep 17 00:00:00 2001 From: Zach Date: Thu, 8 Aug 2024 17:54:43 -0600 Subject: [PATCH 07/13] Create usage-future.md --- documentation/usage-future.md | 153 ++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 documentation/usage-future.md diff --git a/documentation/usage-future.md b/documentation/usage-future.md new file mode 100644 index 0000000..93fd216 --- /dev/null +++ b/documentation/usage-future.md @@ -0,0 +1,153 @@ + +# Future Usage + +`Future` is a core component of the **Later** library that represents a value that will be available asynchronously. It allows you to manage asynchronous tasks that may take time to complete, providing a clean and structured way to handle these operations in Swift. + +## Overview + +A `Future` is used to encapsulate an asynchronous operation that will eventually produce a value or an error. The operation begins running immediately when the `Future` is created. The result of this operation is cached, meaning that once the operation completes, the result (or error) is stored for future accesses. + +### Key Features + +- **Immediate Execution**: The `Future` starts executing its task as soon as it is created. +- **Caching**: The result of the `Future`'s task is cached, so subsequent accesses do not re-execute the task. +- **Error Handling**: The `Future` allows for handling errors both during the task execution and when accessing the result. +- **Cancellation**: A `Future` can be canceled, and such cancellation can be detected via the `.value` property. + +## Example Usage + +### Creating a Future + +You can create a `Future` by defining an asynchronous task. The task runs immediately upon `Future` creation. + +```swift +import Later + +@Sendable func asyncTask() async throws -> String { + return "Hello" +} + +let future = Future { + do { + let result = try await asyncTask() + return result + } catch { + throw error + } +} +``` + +### Accessing the Value + +You can access the value of a `Future` using the `value` property. This is an asynchronous operation that suspends until the result is available. If the task is already completed, the cached result is returned. + +```swift +do { + let result = try await future.value + print(result) // Prints "Hello" if the task succeeded +} catch { + print("Error: \(error)") // Handles cancellation or other errors +} +``` + +### Handling Success and Failure + +You can pass `didSucceed` and `didFail` closures to a `Future` to handle the outcome of the task. These closures are executed when the task completes, before the value is accessed. + +```swift +let future = Future( + didSucceed: { value in + print("Task succeeded with value: \(value)") + }, + didFail: { error in + print("Task failed with error: \(error)") + }, + task: { + try await asyncTask() + } +) +``` + +### Cancellation + +A `Future` can be canceled if it hasn't completed yet. If you try to access the value of a canceled `Future`, it will throw a `CancellationError`. + +```swift +let future = Future { + try await Task.catNap() + return "Completed" +} + +future.cancel() + +do { + _ = try await future.value + Issue.record("Future should have been canceled") +} catch { + _ = try #require(error as? CancellationError) +} +``` + +## Builder Pattern + +The `Future` component in **Later** also supports a builder pattern, allowing for more complex configurations before the `Future` is built and executed. + +### Example Using the Builder + +```swift +import Later + +let future = Future.Builder { + try await Task.catNap() + return "Data fetched" +} +.onSuccess { value in + print("Successfully fetched: \(value)") +} +.onFailure { error in + print("Failed with error: \(error)") +} +.build() + +let result = try await future.value +print(result) // Prints "Data fetched" +``` + +### Error Handling in Builder + +The builder pattern allows you to specify success and failure handlers, which are invoked based on the task's outcome. + +```swift +let future = Future.Builder { + try await Task.catNap() + throw FutureError.mockError +} +.onSuccess { _ in + Issue.record("Task should not succeed") +} +.onFailure { error in + print("Failed with error: \(error)") +} +.build() + +do { + _ = try await future.value + Issue.record("Task should throw an error") +} catch { + print("Caught error: \(error)") +} +``` + +In this example, the `Future` is configured with a task that throws an error. The `onFailure` closure handles this error, while the `onSuccess` closure is not called. + +## Best Practices + +- **Use for Long-Running Operations**: Utilize `Future` for operations like network requests or file I/O that might take time to complete. +- **Handle Cancellations**: Always consider the possibility of a `Future` being canceled and handle `CancellationError` appropriately. +- **Leverage the Builder**: Use the builder pattern for more complex `Future` configurations, especially when you need custom success or failure handling. + +## Conclusion + +`Future` is a powerful tool for managing asynchronous operations in Swift. It simplifies the process of handling tasks that produce values over time, with built-in support for error handling, cancellation, and result caching. The builder pattern adds further flexibility, allowing you to configure your `Future` objects to suit your specific needs. + +Explore other components of the **Later** library, such as [Deferred](usage-deferred.md) and [Stream](usage-stream.md), to continue building your asynchronous programming skills. From df25340084fa29919d11e61c943548beb87a307f Mon Sep 17 00:00:00 2001 From: Zach Date: Thu, 8 Aug 2024 17:58:53 -0600 Subject: [PATCH 08/13] Create usage-deferred.md --- documentation/usage-deferred.md | 165 ++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 documentation/usage-deferred.md diff --git a/documentation/usage-deferred.md b/documentation/usage-deferred.md new file mode 100644 index 0000000..9cbe62d --- /dev/null +++ b/documentation/usage-deferred.md @@ -0,0 +1,165 @@ + +# Deferred Usage + +`Deferred` is a component of the **Later** library that represents a value that will be computed and available asynchronously when explicitly started. Unlike `Future`, a `Deferred` task does not start until you explicitly call `start()`. + +## Overview + +A `Deferred` is ideal for scenarios where you want more control over when an asynchronous operation begins. It allows you to define the operation upfront but defer its execution until a later point in your code. + +### Key Features + +- **Deferred Execution**: The task only begins when `start()` is called. +- **Explicit Control**: You have more control over the timing of the task execution. +- **Error Handling**: Like `Future`, `Deferred` allows for handling errors during task execution and when accessing the result. +- **Cancellation**: A `Deferred` task can only be canceled during its execution. + +## Example Usage + +### Creating a Deferred Task + +You can create a `Deferred` by defining an asynchronous task that will start only when explicitly triggered. + +```swift +import Later + +@Sendable func asyncDeferredTask() async throws -> String { + return "Deferred Hello" +} + +var deferred = Deferred { + do { + let result = try await asyncDeferredTask() + return result + } catch { + throw error + } +} +``` + +### Starting the Deferred Task + +To execute the task, call the `start()` method on the `Deferred` object. + +```swift +deferred.start() +``` + +### Accessing the Value + +You can access the result of a `Deferred` task using the `value` property. This is an asynchronous operation that suspends until the task completes. + +```swift +do { + let result = try await deferred.value + print(result) // Prints "Deferred Hello" if the task succeeded +} catch { + print("Error: \(error)") // Handles any errors that occurred during execution +} +``` + +### Handling Success and Failure + +You can pass `didSucceed` and `didFail` closures to a `Deferred` to handle the outcome of the task. These closures are executed when the task completes, before the value is accessed. + +```swift +var deferred = Deferred( + didSucceed: { value in + print("Task succeeded with value: \(value)") + }, + didFail: { error in + print("Task failed with error: \(error)") + }, + task: { + try await asyncDeferredTask() + } +) +``` + +### Cancellation + +A `Deferred` task can be canceled before it completes. If you try to access the value of a canceled `Deferred`, it will throw a `CancellationError`. + +```swift +var deferred = Deferred { + try await Task.catNap() + return "Completed" +} + +deferred.start() + +deferred.cancel() + +do { + _ = try await deferred.value + Issue.record("Deferred should have been canceled") +} catch { + _ = try #require(error as? CancellationError) +} +``` + +## Builder Pattern + +The `Deferred` component in **Later** also supports a builder pattern, allowing for more complex configurations before the `Deferred` is built and executed. + +### Example Using the Builder + +```swift +import Later + +var deferred = Deferred.Builder { + try await Task.catNap() + return "Data fetched" +} +.onSuccess { value in + print("Successfully fetched: \(value)") +} +.onFailure { error in + print("Failed with error: \(error)") +} +.build() + +deferred.start() + +let result = try await deferred.value +print(result) // Prints "Data fetched" +``` + +### Error Handling in Builder + +The builder pattern allows you to specify success and failure handlers, which are invoked based on the task's outcome. + +```swift +var deferred = Deferred.Builder { + try await Task.catNap() + throw DeferredError.mockError +} +.onSuccess { _ in + Issue.record("Deferred should not succeed") +} +.onFailure { error in + print("Failed with error: \(error)") +} +.build() + +deferred.start() + +do { + _ = try await deferred.value + Issue.record("Task should throw an error") +} catch { + print("Caught error: \(error)") +} +``` + +In this example, the `Deferred` is configured with a task that throws an error. The `onFailure` closure handles this error, while the `onSuccess` closure is not called. + +## Best Practices + +- **Use When Execution Timing Matters**: Utilize `Deferred` when you need control over when an asynchronous task starts. +- **Handle Cancellations**: Consider the possibility of a `Deferred` task being canceled and handle `CancellationError` appropriately. +- **Leverage the Builder**: Use the builder pattern for more complex `Deferred` configurations, especially when you need custom success or failure handling. + +## Conclusion + +`Deferred` is a powerful tool for managing asynchronous operations that require explicit control over their start time. It simplifies the process of handling tasks that need to be deferred, with built-in support for error handling, cancellation, and flexible configuration through the builder pattern. Explore other components of the **Later** library, such as [Future](usage-future.md) and [Stream](usage-stream.md), to continue building your asynchronous programming skills. From c8bb2a855adcf7b6f4b8072c43542805e820015a Mon Sep 17 00:00:00 2001 From: Zach Date: Thu, 8 Aug 2024 18:03:42 -0600 Subject: [PATCH 09/13] Create usage-stream.md --- documentation/usage-stream.md | 206 ++++++++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 documentation/usage-stream.md diff --git a/documentation/usage-stream.md b/documentation/usage-stream.md new file mode 100644 index 0000000..b7bfb97 --- /dev/null +++ b/documentation/usage-stream.md @@ -0,0 +1,206 @@ + +# Stream Usage + +`Stream` is a powerful component of the **Later** library that represents an asynchronous sequence of values emitted over time. It is particularly useful for handling data that updates periodically or in response to external events. + +## Overview + +A `Stream` allows you to work with a sequence of values that are produced asynchronously. You can subscribe to a `Stream` and react to each value as it is emitted, handling them in a sequential manner. + +### Key Features + +- **Asynchronous Value Emission**: Values are emitted over time and can be handled asynchronously. +- **Transformation Operations**: `Stream` supports common operations like `map`, `filter`, and `compactMap`. +- **Error Handling**: Streams can handle errors that occur during value emission. +- **Completion Handling**: Streams can signal the successful completion of value emission. + +## Example Usage + +### Creating a Stream + +You can create a `Stream` by defining an emitter block that produces values over time. The block runs asynchronously and emits values using the `emitter.emit(value:)` method. + +```swift +import Later + +let stream = Stream { emitter in + try await Task.catNap() + emitter.emit(value: "First value") + try await Task.catNap() + emitter.emit(value: "Second value") +} +``` + +### Subscribing to a Stream + +You can subscribe to a `Stream` and react to each emitted value using `forEach`. + +```swift +var values: [String] = [] +try await stream.forEach { value in + values.append(value) +} +print(values) // Prints ["First value", "Second value"] +``` + +### Transforming Streams + +Streams in **Later** support various transformation operations, including `map`, `filter`, and `compactMap`. + +#### Mapping Values + +The `map` function allows you to transform each value emitted by the stream. + +```swift +let mappedStream = stream.map { value in + "Mapped \(value)" +} + +var mappedValues: [String] = [] +try await mappedStream.forEach { value in + mappedValues.append(value) +} +print(mappedValues) // Prints ["Mapped First value", "Mapped Second value"] +``` + +#### Filtering Values + +The `filter` function allows you to only include values that meet certain criteria. + +```swift +let filteredStream = stream.filter { value in + value.contains("First") +} + +var filteredValues: [String] = [] +try await filteredStream.forEach { value in + filteredValues.append(value) +} +print(filteredValues) // Prints ["First value"] +``` + +#### Compact Mapping Values + +The `compactMap` function allows you to transform and filter out `nil` values. + +```swift +let compactMappedStream = stream.compactMap { value in + value == "First value" ? value : nil +} + +var compactMappedValues: [String] = [] +try await compactMappedStream.forEach { value in + compactMappedValues.append(value) +} +print(compactMappedValues) // Prints ["First value"] +``` + +### Handling Errors + +Streams can handle errors that occur during value emission using a `do-catch` block. + +```swift +let stream = Stream { emitter in + try await Task.catNap() + emitter.emit(value: "First value") + try await Task.catNap() + emitter.emit(value: "Second value") + throw StreamError.mockError // Simulate an error +} + +do { + var values = [String]() + for try await value in stream { + values.append(value) + } +} catch { + print("Stream error: \(error)") +} +``` + +### Handling Completion + +You can handle the successful completion of a stream using the `onSuccess` closure. + +```swift +let stream = Stream { emitter in + try await Task.catNap() + emitter.emit(value: "Only value") +} + +stream.onSuccess { + print("Stream completed successfully") +} +``` + +## Builder Pattern + +The `Stream` component in **Later** also supports a builder pattern, allowing for more complex configurations before the `Stream` is built and executed. + +### Example Using the Builder + +```swift +import Later + +let stream = Stream.Builder { emitter in + try await Task.catNap() + emitter.emit(value: "First value") + try await Task.catNap() + emitter.emit(value: "Second value") +} +.onSuccess { + print("Stream completed successfully") +} +.onFailure { error in + print("Stream failed with error: \(error)") +} +.build() + +var values = [String]() +do { + for try await value in stream { + values.append(value) + } +} catch { + print("Stream error: \(error)") +} + +print(values) // Prints ["First value", "Second value"] +``` + +### Error Handling in Builder + +The builder pattern allows you to specify success and failure handlers, which are invoked based on the stream's outcome. + +```swift +let stream = Stream.Builder { emitter in + try await Task.catNap() + throw StreamError.mockError +} +.onSuccess { + Issue.record("Stream should not succeed") +} +.onFailure { error in + print("Failed with error: \(error)") +} +.build() + +var values = [String]() +do { + for try await value in stream { + values.append(value) + } +} catch { + print("Stream error: \(error)") +} +``` + +## Best Practices + +- **Use for Asynchronous Data**: Utilize `Stream` when dealing with data that updates over time or in response to events. +- **Handle Errors Appropriately**: Implement robust error handling to manage any issues that may arise during value emission. +- **Leverage the Builder**: Use the builder pattern for more complex `Stream` configurations, especially when you need custom success or failure handling. + +## Conclusion + +`Stream` is a versatile component for managing asynchronous sequences of values in Swift. It simplifies the process of reacting to and transforming values over time, with built-in support for error handling, completion handling, and flexible configuration through the builder pattern. Explore other components of the **Later** library, such as [Future](usage-future.md) and [Deferred](usage-deferred.md), to continue building your asynchronous programming skills. From e7ce9a30a122cfa31fdc1b87d8101c87be3502ec Mon Sep 17 00:00:00 2001 From: Zach Date: Thu, 8 Aug 2024 18:16:12 -0600 Subject: [PATCH 10/13] Update usage-sendablevalue.md --- documentation/usage-sendablevalue.md | 119 ++++++++++++++++++++------- 1 file changed, 91 insertions(+), 28 deletions(-) diff --git a/documentation/usage-sendablevalue.md b/documentation/usage-sendablevalue.md index 213d52a..f8681e1 100644 --- a/documentation/usage-sendablevalue.md +++ b/documentation/usage-sendablevalue.md @@ -7,65 +7,128 @@ The `SendableValue` type ensures that the value it holds is safely accessible in a concurrent environment. It uses [`OSAllocatedUnfairLock`](https://developer.apple.com/documentation/os/osallocatedunfairlock) to manage locking, making it efficient and secure for multi-threaded operations. -## Example Usage +### Key Features + +- **Thread-Safe Mutability**: Ensures values can be safely mutated across concurrent tasks. +- **Concurrency Control**: Built-in locking mechanisms manage access in a multi-threaded environment. +- **Compatibility with `Sendable`**: Allows classes or structs using `SendableValue` to be marked as `Sendable` even when they contain mutable state. -Here’s how to use `SendableValue` in a typical scenario: +## Example Usage -### Creating and Setting a Value +### Using SendableValue in a Sendable Class -You can create a `SendableValue` by initializing it with a value. You can then set the value asynchronously using the `set(value:)` method. +One of the most common use cases for `SendableValue` is in a `Sendable` class, where you need to safely share mutable state across multiple tasks. ```swift import Later -let sendableValue = SendableValue(42) -sendableValue.set(value: 100) -``` +final class Counter: Sendable { + private let value: SendableValue -### Accessing the Value Asynchronously + init(initialValue: Int) { + self.value = SendableValue(initialValue) + } -The value stored in `SendableValue` can be accessed asynchronously using the `value` property. + func increment() { + value.update { $0 += 1 } + } -```swift -let value = await sendableValue.value -print(value) // Prints "100" -``` + func getValue() async -> Int { + await value.value + } +} -### Ensuring Thread Safety +let counter = Counter(initialValue: 0) -Because `SendableValue` is designed to be thread-safe, you can confidently use it in environments where multiple tasks might attempt to read or write the value simultaneously. +// Increment concurrently +let tasks = (1 ... 10).map { _ in + Task { + counter.increment() + } +} -### Full Example +for task in tasks { + await task.value +} + +let finalValue = await counter.getValue() +print(finalValue) // Prints "10" +``` + +In this example, `Counter` is a `Sendable` class that safely manages a mutable integer value using `SendableValue`. The value is incremented concurrently across multiple tasks, demonstrating thread-safe access and mutation. -Here’s a complete example that demonstrates creating a `SendableValue`, updating it, and accessing it from an asynchronous context: +### Safely Sharing Mutable State + +Another key use of `SendableValue` is to allow safe sharing of mutable state in `Sendable` contexts, where direct use of `var` would otherwise make the class or struct non-conformant. ```swift -import Later +final class SharedData: Sendable { + private let data: SendableValue -let sendableValue = SendableValue(42) + init(initialData: String) { + self.data = SendableValue(initialData) + } -// Update the value asynchronously -Task { - sendableValue.set(value: 100) + func updateData(newData: String) { + data.set(value: newData) + } + + func getData() async -> String { + await data.value + } } -// Access the value asynchronously +let sharedData = SharedData(initialData: "Initial Value") + +// Update data safely and ensure the update is completed before accessing the value Task { - let value = await sendableValue.value - print(value) // Prints "100" + sharedData.updateData(newData: "Updated Value") + let currentValue = await sharedData.getData() + print(currentValue) // Prints "Updated Value" } ``` -In this example, the value is set and then accessed in separate asynchronous tasks, showcasing the thread safety and ease of use provided by `SendableValue`. +This updated example ensures that the value is only accessed after the task responsible for updating the data has completed, preventing any indeterminate results. + +### Ensuring Safe Access in Actors + +`SendableValue` can also be used in actors to manage mutable state safely and efficiently. + +```swift +actor SafeCounter { + private let value: SendableValue + + init(initialValue: Int) { + self.value = SendableValue(initialValue) + } + + func increment() { + value.update { $0 += 1 } + } + + func getValue() async -> Int { + await value.value + } +} + +let safeCounter = SafeCounter(initialValue: 0) + +// Increment the counter safely within the actor +await safeCounter.increment() +let safeValue = await safeCounter.getValue() +print(safeValue) // Prints "1" +``` + +In this actor example, `SendableValue` provides additional safety and efficiency, ensuring that the state is not only protected by the actor’s isolation but also optimized for concurrent access. ## Best Practices - **Use in Concurrent Environments**: Utilize `SendableValue` whenever you need to share and mutate values across multiple concurrent tasks. -- **Avoid Excessive Locking**: Although `SendableValue` handles locking internally, be mindful of performance when frequently updating the value in a highly concurrent environment. +- **Incorporate in `Sendable` Classes**: Use `SendableValue` in `Sendable` classes to safely manage mutable state without violating `Sendable` conformance. +- **Combine with Actors**: Consider using `SendableValue` within actors to enhance safety and efficiency in managing mutable state. ## Conclusion `SendableValue` is an essential component of the **Later** library that provides a simple, safe way to manage mutable state in asynchronous Swift programming. By using `SendableValue`, you can ensure that your values are handled correctly across concurrent tasks, making your code more robust and maintainable. For more advanced usage, explore other components of the **Later** library like [Future](usage-future.md), [Deferred](usage-deferred.md), and [Stream](usage-stream.md). - From 6da9f5f52b8ea6ba83d3d531a5447038d17d98c8 Mon Sep 17 00:00:00 2001 From: Zach Date: Thu, 8 Aug 2024 18:33:20 -0600 Subject: [PATCH 11/13] Create usage-publisher.md --- documentation/usage-publisher.md | 187 +++++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 documentation/usage-publisher.md diff --git a/documentation/usage-publisher.md b/documentation/usage-publisher.md new file mode 100644 index 0000000..97aeca6 --- /dev/null +++ b/documentation/usage-publisher.md @@ -0,0 +1,187 @@ + +# Publisher Usage + +`Publisher` is a component of the **Later** library that allows you to manage and notify subscribers about changes to a value over time. It is particularly useful for implementing reactive systems where multiple components need to be kept in sync with changing data. + +## Overview + +A `Publisher` allows you to manage a value and notify any subscribers whenever that value changes. Subscribers can be added or removed dynamically, and they will be updated whenever the publisher’s value is modified. + +### Key Features + +- **Subscriber Notification**: Automatically notifies subscribers when the value changes. +- **Multiple Subscribers**: Supports multiple subscribers receiving updates. +- **Integration with `Future`, `Deferred`, and `Stream`**: Can generate `Future`, `Deferred`, and `Stream` objects directly, ensuring that these objects and the `Publisher` stay synchronized. + +## Example Usage + +### Creating a Publisher and Adding Subscribers + +You can create a `Publisher` by initializing it with an optional initial value. Subscribers can be added using the `add(subscriber:)` method. + +```swift +import Later + +final class TestSubscriber: Subscribing, Sendable { + typealias Value = String + let observedValues: SendableValue<[String?]> = SendableValue([]) + + func didUpdate(newValue: String?) { + observedValues.update { values in + values.append(newValue) + } + } +} + +let publisher = Publisher(initialValue: "Initial value") +let testSubscriber = TestSubscriber() +await publisher.add(subscriber: testSubscriber) + +// Update the value using a Future generated from the publisher +let future = await publisher.future( + didSucceed: nil, + didFail: nil, + task: { + return "Updated value" + } +) + +let value = try await future.value +let values = await testSubscriber.observedValues.value +print(values) // Prints ["Initial value", "Updated value"] +``` + +In this example, the `TestSubscriber` class conforms to the `Subscribing` protocol, which allows it to observe changes in the publisher's value. The `Publisher` notifies the subscriber whenever the value is updated via the `Future`. + +### Handling Multiple Subscribers + +`Publisher` can handle multiple subscribers simultaneously, ensuring that all subscribers are notified when the value changes. + +```swift +let publisher = Publisher(initialValue: "Initial value") + +let testSubscriber1 = TestSubscriber() +let testSubscriber2 = TestSubscriber() + +await publisher.add(subscriber: testSubscriber1) +await publisher.add(subscriber: testSubscriber2) + +var deferred = await publisher.deferred( + didSucceed: nil, + didFail: nil, + task: { + return "Updated value for all" + } +) + +deferred.start() + +try await deferred.value + +let values1 = await testSubscriber1.observedValues.value +let values2 = await testSubscriber2.observedValues.value + +print(values1) // Prints ["Initial value", "Updated value for all"] +print(values2) // Prints ["Initial value", "Updated value for all"] +``` + +Both subscribers receive the same updates, ensuring that their observed values are consistent. + +### Using Publisher Without Subscribers + +Even if there are no subscribers, a `Publisher` can still be used to manage a value. You can access the current value directly, typically via an operation like a `Future`, `Deferred`, or `Stream`. + +```swift +let publisher = Publisher() + +let future = await publisher.future( + didSucceed: nil, + didFail: nil, + task: { + return "Updated value" + } +) + +let value = try await future.value +let currentValue = await publisher.currentValue +print(currentValue) // Prints "Updated value" +``` + +This demonstrates that `Publisher` can still manage and update values even when no subscribers are present, but updates are typically done through operations like `Future`. + +### Integrating Publisher with Future + +`Publisher` can generate a `Future` that is synchronized with the publisher’s updates. The `Future` will complete when the task completes, and the `Publisher` will update its value accordingly. + +```swift +let future = await publisher.future( + didSucceed: nil, + didFail: nil, + task: { + try await Task.catNap() + return "Future completed" + } +) + +let value = try await future.value +let values = await testSubscriber.observedValues.value + +print(values) // Prints [nil, "Future completed"] +print(value) // Prints "Future completed" +``` + +### Integrating Publisher with Deferred + +Similarly, a `Deferred` can be generated from a `Publisher`, which will update the `Publisher` when the `Deferred` task is completed. + +```swift +var deferred = await publisher.deferred( + didSucceed: nil, + didFail: nil, + task: { + try await Task.catNap() + return "Deferred completed" + } +) + +deferred.start() + +let value = try await deferred.value +let values = await testSubscriber.observedValues.value + +print(values) // Prints [nil, "Deferred completed"] +print(value) // Prints "Deferred completed" +``` + +### Integrating Publisher with Stream + +`Publisher` and `Stream` can be used together by generating a `Stream` from the `Publisher`. This stream will emit values as the `Publisher` updates. + +```swift +let stream = await publisher.stream( + didSucceed: nil, + didFail: nil, + task: { emitter in + await Task.catNap() + emitter.emit(value: "Stream value 1") + await Task.catNap() + emitter.emit(value: "Stream value 2") + } +) + +for try await value in stream { + print("Stream received: \(value ?? "nil")") +} +``` + +In this example, the `Publisher` updates are synchronized with the `Stream`, allowing subscribers to react to an ongoing sequence of changes. + +## Best Practices + +- **Use for Reactive Systems**: Utilize `Publisher` to keep multiple components in sync with changing data. +- **Manage Subscribers Effectively**: Be mindful of adding and removing subscribers to avoid memory leaks or unnecessary updates. +- **Combine with Future, Deferred, and Stream**: Use `Publisher` to generate and synchronize `Future`, `Deferred`, and `Stream` objects, ensuring all components stay in sync. + +## Conclusion + +`Publisher` is a versatile component for managing and notifying subscribers about changes in data over time. It simplifies the implementation of reactive systems, ensuring that multiple components can stay synchronized with the latest data. Explore other components of the **Later** library, such as [Future](usage-future.md), [Deferred](usage-deferred.md), and [Stream](usage-stream.md), to continue building your asynchronous programming skills. From 2802192069ef5c322dc3a18529f3add8856b500c Mon Sep 17 00:00:00 2001 From: Zach Date: Thu, 8 Aug 2024 18:36:49 -0600 Subject: [PATCH 12/13] Create usage-schedule-task.md --- documentation/usage-schedule-task.md | 47 ++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 documentation/usage-schedule-task.md diff --git a/documentation/usage-schedule-task.md b/documentation/usage-schedule-task.md new file mode 100644 index 0000000..0553ab6 --- /dev/null +++ b/documentation/usage-schedule-task.md @@ -0,0 +1,47 @@ +# Task Scheduling Extension + +This extension to the `Task` class provides a convenient way to schedule a task to be executed after a specified duration. It is particularly useful for scenarios where you need to delay the execution of a task for a specific period. + +## Overview + +The `Task.schedule` function allows you to define a task that will execute after a specified duration. This is especially useful for timed operations, such as delaying a retry attempt or performing an operation after a certain amount of time has passed. + +### Key Features + +- **Delayed Execution**: Execute a task after a specified duration. +- **Tolerance Handling**: Optionally specify a tolerance for the duration. +- **Clock Customization**: Use a custom clock for measuring the duration. +- **Error Handling**: Handles errors that might occur during task execution or if the sleep is interrupted. + +## Example Usage + +Here is an example of how to use `Task.schedule` to execute a task after a delay of 5 seconds: + +```swift +do { + try await Task.schedule(for: .seconds(5)) { + print("Task executed after 5 seconds") + } +} catch { + print("Failed to execute task: \(error)") +} +``` + +In this example, the task prints a message to the console after waiting for 5 seconds. If the task fails or the sleep is interrupted, the error is caught and handled. + +### Parameters + +- `duration`: The duration to wait before executing the task. This parameter defines how long the task will be delayed. +- `tolerance`: An optional tolerance for the duration. This allows for a small variation in the timing if needed. +- `clock`: The clock to use for measuring the duration. Defaults to `ContinuousClock`, which provides a continuous time reference that is ideal for most scheduling tasks. +- `task`: The task to execute after the specified duration. This is an asynchronous function that can throw errors. + +### Availability + +This method is available on the following platforms: +- macOS 13.0 or newer +- iOS 16.0 or newer +- watchOS 9.0 or newer +- tvOS 16.0 or newer + +Ensure that your project targets the appropriate platform versions to use this feature. From c6dacfe03802075022e5bd5d63268bd963ac3c53 Mon Sep 17 00:00:00 2001 From: Zach Date: Thu, 8 Aug 2024 18:40:07 -0600 Subject: [PATCH 13/13] Create contributing.md --- documentation/contributing.md | 60 +++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 documentation/contributing.md diff --git a/documentation/contributing.md b/documentation/contributing.md new file mode 100644 index 0000000..c634b60 --- /dev/null +++ b/documentation/contributing.md @@ -0,0 +1,60 @@ +# Contributing to Later + +Thank you for considering contributing to **Later**! Your contributions help make this project better for everyone. + +## How to Contribute + +### 1. Reporting Bugs + +If you encounter any bugs, please open an issue on GitHub. When reporting a bug, please include: + +- A clear and descriptive title. +- A detailed description of the bug, including steps to reproduce it. +- The expected behavior and what actually happened. +- The version of **Later** you are using. +- Any relevant screenshots or logs. + +### 2. Suggesting Features + +We welcome new ideas! If you have a feature you'd like to see added to **Later**, please open an issue and describe: + +- The problem the feature would solve. +- How you think the feature should work. +- Any additional context or examples that would help illustrate your idea. + +### 3. Submitting Pull Requests + +If you'd like to contribute code to **Later**, follow these steps: + +1. **Fork the Repository**: Create a personal fork of the **Later** repository on GitHub. +2. **Clone Your Fork**: Clone your fork to your local machine: + ```bash + git clone https://github.com/your-username/Later.git + ``` +3. **Create a New Branch**: Create a new branch for your feature or bugfix: + ```bash + git checkout -b my-feature-branch + ``` +4. **Make Changes**: Implement your changes in the new branch. +5. **Test Your Changes**: Ensure your changes pass all tests. Add new tests if necessary. +6. **Commit Your Changes**: Commit your changes with a descriptive commit message: + ```bash + git commit -m "Add my new feature" + ``` +7. **Push to GitHub**: Push your branch to your GitHub fork: + ```bash + git push origin my-feature-branch + ``` +8. **Create a Pull Request**: Go to the **Later** repository on GitHub and create a pull request from your branch. + +### 4. Code Style + +Please follow the coding style guidelines used in the **Later** project. Consistent code style helps make the codebase more maintainable and easier to review. + +### 5. License + +By contributing to **Later**, you agree that your contributions will be licensed under the same license as the project: [MIT License]([LICENSE](https://github.com/0xLeif/Later/blob/main/LICENSE)). + +## Thank You! + +Your contributions are highly valued and appreciated. Thank you for helping improve **Later**!