CombineAsync is a collection of Combine extensions and utilities designed for asynchronous tasks.
To run the example project, clone the repo, and run pod install
from the Example directory first.
- Swift 5.5 or higher
- iOS 13.0 or higher
- MacOS 10.15 or higher
- TVOS 13.0 or higher
- WatchOS 8.0 or higher
- Xcode 13 or higher
You can easily install CombineAsync via CocoaPods. Add the following line to your Podfile:
pod 'CombineAsync', '~> 2.0'
To install using Xcode's Swift Package Manager, follow these steps:
- Go to File > Swift Package > Add Package Dependency
- Enter the URL: https://github.com/hainayanda/CombineAsync.git
- Choose Up to Next Major for the version rule and set the version to 1.6.0.
- Click "Next" and wait for the package to be fetched.
If you prefer using Package.swift, add CombineAsync as a dependency in your Package.swift file:
dependencies: [
.package(url: "https://github.com/hainayanda/CombineAsync.git", .upToNextMajor(from: "1.6.0"))
]
Then, include it in your target:
.target(
name: "MyModule",
dependencies: ["CombineAsync"]
)
CombineAsync
provides various extensions and utilities for working with Combine
and Swift async. Here are some of the key features:
Convert any object that implements Publisher
into Swift async with a single call:
// Implicitly await with a 30-second timeout
let result = await publisher.sinkAsynchronously()
// Specify a timeout explicitly
let timedResult = await publisher.waitForOutput(timeout: 1)
// No timeout
let noTimeoutResult = await publisher.waitForOutputIndefinitely()
Convert a sequence of Publisher
into async with a single call:
// Implicitly await with a 30-second timeout
let results = await arrayOfPublishers.sinkAsynchronously()
// Specify a timeout
let timedResults = await arrayOfPublishers.waitForOutputs(timeout: 1)
// No timeout
let noTimeoutResult = await arrayOfPublishers.waitForOutputsIndefinitely()
Convert Swift async code into a Future
object with ease:
let future = Future {
try await getSomethingAsync()
}
Execute asynchronous code inside a sink without explicitly creating a Task
:
publisher.asyncSink { output in
await somethingAsync(output)
}
Convert old asynchronous code using withCheckedThrowingContinuation
with timeout:
// Automatically fail after 30 seconds
try await withCheckedThrowingContinuation(timeout: 30) { continuation in
doLongOperation { result in
continuation.resume(returning: result)
}
}
Execute asynchronous code inside a sink and make it debounced if its still running asyncrhonous task:
publisher.debounceAsyncSink { output in
await somethingAsync(output)
}
Automatically release a closure in the sink after a specified duration or when an object is released:
publisher.autoReleaseSink(timeout: 60) { _ in
// Handle completion
} receiveValue: {
// Handle value reception
}
Assign without retaining the class instance by using autoReleaseAssign
or weakAssign
instead of assign:
// with cancellable
let cancellable = publisher.weakAssign(to: \.property, on: object)
// with auto release cancellable
publisher.autoReleaseAssign(to: \.property, on: object)
Recover from errors using three different methods:
// Ignore errors and produce AnyPublisher<Output, Never>
publisher.ignoreError()
// Convert errors to output and produce AnyPublisher<Output, Never>
publisher.replaceError { error in convertErrorToOutput(error) }
// Attempt to convert errors to output and produce AnyPublisher<Output, Failure>
publisher.replaceErrorIfNeeded { error in convertErrorToOutputIfNeeded(error) }
Map asynchronously using CombineAsync
:
publisher.asyncMap { output in
await convertOutputAsynchronously(output)
}
There are some async map method you can use:
asyncMap
which is equivalent withmap
but asynchronousasyncTryMap
which is equivalent withtryMap
but asynchronousasyncCompactMap
which is equivalent withcompactMap
but asynchronousasyncTryCompactMap
which is equivalent withtryCompactMap
but asynchronous
Map elements of a Publisher
with a Sequence
as its output:
myArrayPublisher.mapSequence { element in
// Map the element of the sequence to another type
}
Those line of code are equivalent with:
myArrayPublisher.map { output in
output.map { element in
// do map the element of the sequence to another type
}
}
All of the sequence mapper that you can use are:
mapSequence(_:)
which bypassSequence.map(_:)
compactMapSequence(_:)
which bypassSequence.compactMap(_:)
tryMapSequence(_:)
which bypassSequence.map(_:)
but with throwing mapper closuretryCompactMapSequence(_:)
which bypassSequence.compactMap(_:)
but with throwing mapper closureasyncMapSequence(_:)
which bypassSequence.asyncMap(_:)
asyncCompactMapSequence(_:)
which bypassSequence.asyncCompactMap(_:)
Feel free to contribute by cloning the repository and creating a pull request.
Nayanda Haberty, hainayanda@outlook.com
CombineAsync is available under the MIT license. See the LICENSE file for more info.