Contents

serhanaksut/swift-async-collections

Async and concurrent extensions for Swift's `Sequence` type, built on structured concurrency.

Features

  • Sequential async

- asyncMap - asyncCompactMap - asyncFlatMap - asyncForEach - asyncFilter - asyncReduce - asyncContains - asyncAllSatisfy - asyncFirst(where:) - asyncPrefix(while:) - asyncDrop(while:)

  • Concurrent

- concurrentMap - concurrentCompactMap - concurrentFlatMap - concurrentForEach - concurrentFilter - concurrentContains - concurrentAllSatisfy - concurrentFirst(where:)

  • Order-preserving results for all map/compactMap/flatMap/filter/first variants
  • Optional maxNumberOfTasks to limit parallelism
  • Automatic cancellation propagation via TaskGroup
  • Throwing variants with first-error cancellation via ThrowingTaskGroup
  • Swift 6 strict concurrency compatible

Installation

Swift Package Manager

Add to your Package.swift:

dependencies: [
    .package(url: "https://github.com/serhanaksut/swift-async-collections.git", from: "1.3.0")
]

Then add "AsyncCollections" to your target's dependencies:

.target(name: "YourTarget", dependencies: ["AsyncCollections"])

Usage

Sequential async operations

Process elements one at a time, awaiting each before starting the next:

let data = try await urls.asyncMap { try await URLSession.shared.data(from: $0).0 }

let validUsers = try await ids.asyncCompactMap { try await fetchUserIfExists(id: $0) }

let allPosts = try await users.asyncFlatMap { try await fetchPosts(for: $0) }

try await items.asyncForEach { try await save($0) }

let activeUsers = try await users.asyncFilter { try await checkIsActive($0) }

let total = try await invoices.asyncReduce(0) { sum, invoice in
    try await sum + fetchAmount(for: invoice)
}

let usersByID = try await users.asyncReduce(into: [:]) { dict, user in
    dict[user.id] = try await fetchProfile(for: user)
}

let hasAdmin = try await users.asyncContains { try await checkIsAdmin($0) }

let allVerified = try await users.asyncAllSatisfy { try await checkIsVerified($0) }

let firstAdmin = try await users.asyncFirst { try await checkIsAdmin($0) }

let leadingActive = try await users.asyncPrefix { try await checkIsActive($0) }

let afterInactive = try await users.asyncDrop { try await checkIsInactive($0) }

Concurrent operations

Process all elements in parallel using structured concurrency:

let data = try await urls.concurrentMap { try await URLSession.shared.data(from: $0).0 }

let validUsers = try await ids.concurrentCompactMap { try await fetchUserIfExists(id: $0) }

let allPosts = try await users.concurrentFlatMap { try await fetchPosts(for: $0) }

try await items.concurrentForEach { try await upload($0) }

let activeUsers = try await users.concurrentFilter { try await checkIsActive($0) }

let hasAdmin = try await users.concurrentContains { try await checkIsAdmin($0) }

let allVerified = try await users.concurrentAllSatisfy { try await checkIsVerified($0) }

let firstAdmin = try await users.concurrentFirst { try await checkIsAdmin($0) }

Limiting parallelism

Use maxNumberOfTasks to cap the number of concurrent operations:

// At most 5 downloads at a time
let images = try await urls.concurrentMap(maxNumberOfTasks: 5) {
    try await downloadImage(from: $0)
}

Requirements

  • Swift 6.0+
  • iOS 16+ / macOS 13+ / tvOS 16+ / watchOS 9+ / visionOS 1+

License

MIT - see LICENSE for details.

Package Metadata

Repository: serhanaksut/swift-async-collections

Default branch: main

README: README.md