← Back to list

Exploring Your First Async Sequences

Introduction to Async Sequences

abdul ahad · 2025-03-18 19:23 · 0 claps · 3.4 min read
#asyncsequence #sequencing #swift-concurrency #ios #swift
Open on Medium ↗
Wiki topics: GEN · Genomics & Sequencing 📱 · Mobile Development

Exploring Your First Async Sequences

Photo by Aziz Acharki on Unsplash

Photo by Aziz Acharki on Unsplash

Async Sequences

  • In Swift, sequences allow iteration over a collection of values, such as arrays or dictionaries, using a for loop.
  • An async sequence (AsyncSequence) extends this concept for asynchronous operations, where not all values in the sequence are available immediately.
  • Async sequences are ideal for streaming data, like reading lines from a file or receiving chunks of data over a network.

Sync vs. Async Loop

Synchronous Loop:

A typical synchronous loop processes all elements that are already available:

let myArray = [1, 2, 3]
for number in myArray {
    print(number)
}

Asynchronous Loop:

An async loop processes elements as they become available, awaiting new data:

for try await line in csvURL.lines {
    print(line)
}
  • The loop suspends execution until the next value is available.

Practical Example: Reading a CSV File

Scenario:

You need to read and process a large CSV file line by line, where:

  • Each line represents a record (e.g., a car’s details).
  • Lines arrive asynchronously, such as over a network.

Basic Synchronous Parsing:

let csvData = try Data(contentsOf: url)
let csvString = String(data: csvData, encoding: .utf8)
let csvLines = csvString?.components(separatedBy: "\n") ?? []

for line in csvLines {
    let components = line.components(separatedBy: ",")
    guard components.count == 4 else { continue }

    let car = Car(year: components[0], make: components[1], model: components[2], body_styles: components[3])
    cars.append(car)
}
print(cars)

Problems with Synchronous Parsing:

Memory Usage:

  • The entire file is loaded into memory before processing.
  • Inefficient for large files.

Blocking:

  • The main thread is blocked while reading and processing the file.

Network Inefficiency:

  • When fetching from a URL, we wait for the entire file to download before starting any processing.

Using Async Sequences

Parsing Lines Asynchronously:

With AsyncSequence, you can process lines as they are received:

var cars = [Car]()

for try await line in csvURL.lines {
    let components = line.components(separatedBy: ",")
    guard components.count == 4 else { continue }
    let car = Car(year: components[0], make: components[1], model: components[2], body_styles: components[3])
    cars.append(car)
}
print(cars)

Key Difference:

  • The lines property on URL returns an AsyncSequence.
  • Each line is fetched and processed as soon as it is available, minimizing memory usage and improving efficiency.

Control Flow in Async Loops

Async loops work like regular loops:

  • **break**: Exit the loop early.
  • **continue**: Skip the current iteration.
  • Error Handling: Handle errors with do-catch blocks.

Example:

do {
    for try await line in csvURL.lines {
        guard !line.isEmpty else { continue }
        print(line)
    }
} catch {
    print("Failed to process lines:", error)
}

Parallelizing Async Loops

Sequential Execution:

By default, async loops run sequentially:

for try await line in csvURLPartOne.lines {
    // Process part one
}
for try await line in csvURLPartTwo.lines {
    // Process part two
}
  • The second loop starts only after the first completes.
  • Async sequences behave like regular loops in terms of execution order

Running Loops in Parallel:

To run loops in parallel, use separate Tasks:

Task {
    for try await line in csvURLPartOne.lines {
        print("Part one:", line)
    }
}

Task {
    for try await line in csvURLPartTwo.lines {
        print("Part two:", line)
    }
}
  • Limitations: Without additional handling, it’s unclear when both tasks complete.

While this works to run code in parallel, it does prevent us from knowing when both tasks are completed in a nice way. We can fix this by assigning these two tasks to their own variables and awaiting their values:

Awaiting Multiple Tasks:

Combine tasks for structured concurrency:

Task {
    let task1 = Task {
        for try await _ in csvURLPartOne.lines {
            print("Part one processing...")
        }
    }

    let task2 = Task {
        for try await _ in csvURLPartTwo.lines {
            print("Part two processing...")
        }
    }

    try await (task1.value, task2.value)
    print("Both parts processed")
}

Using AsyncSequence Transformations

Async sequences support functional transformations, similar to synchronous sequences. For example:

Mapping Values:

let sequence = csvURL.lines.map { line -> Car in
    let components = line.components(separatedBy: ",")
    guard components.count == 4 else {
        return Car(year: "", make: "", model: "", body_styles: "")
    }
    return Car(year: components[0], make: components[1], model: components[2], body_styles: components[3])
}

for try await car in sequence {
    cars.append(car)
}

How It Works:

  • Each line is transformed into a Car object as it is received.
  • The transformed AsyncSequence emits Car objects instead of raw lines.

Other Operations:

  • **filter**: Skip elements that don’t meet a condition.
  • **flatMap**: Transform elements into sequences and flatten them.

Handling Errors in Async Sequences

Errors in async sequences are handled like errors in functions:

do {
    for try await line in csvURL.lines {
        print(line)
    }
} catch {
    print("Error:", error)
}
  • If an error occurs, the loop ends, and control passes to the catch block.

Key Takeaways

Async Sequences vs. Sequences:

  • Sequence: Processes all values that are already available.
  • AsyncSequence: Processes values as they become available asynchronously.

Advantages of Async Sequences:

  • Efficient memory usage by processing data incrementally.
  • Useful for streaming data (e.g., reading files, network responses).

Transformations:

  • Use map, filter, and similar operations to process async sequences.

Parallel Execution:

  • Async loops execute sequentially unless explicitly parallelized with tasks.

Structured Concurrency:

  • Use TaskGroup or async let for cleaner parallel execution (discussed further in later chapters).

By leveraging async sequences, you can write efficient, readable, and concurrent Swift code for scenarios involving streaming data.


메타데이터
post_id
27873e66f303
slug
exploring-your-first-async-sequences-27873e66f303
url
https://medium.com/@abdulahd1996/exploring-your-first-async-sequences-27873e66f303
canonical_url
https://medium.com/@abdulahd1996/exploring-your-first-async-sequences-27873e66f303
author_url
https://medium.com/@abdulahd1996
status
ok
fetched_at
2026-06-21 19:25:17