Swift 5.5: Asynchronously Iterating Line-By-Line Through a File

Swift 5.5: Asynchronously iterating line-by-line through a file

The main thing they added that enables this, is AsyncSequence. AsyncSequence is like Sequence, but its Iterator.next method is async throws.

Specifically, you can use URLSession.AsyncBytes.lines to get an AsyncSequence of the lines in a file.

Suppose you are in an async throws method, you can do:

let (bytes, response) = try await URLSession.shared.bytes(from: URL(string: "file://...")!)
for try await line in bytes.lines {
// do something...
}

Note that there is also FileHandle.AsyncBytes.lines, but in the documentation it says:

Rather than creating a FileHandle to read a file asynchronously, you can instead use a file:// URL in combination with the async-await methods in URLSession. These include the bytes(for:delegate:) and bytes(from:delegate:) methods that deliver an asynchronous sequence of bytes, and data(for:delegate:) and data(from:delegate:) to return the file’s entire contents at once.

Swift Async let with loop

You can use a task group. See Tasks and Task Groups section of the The Swift Programming Language: Concurrency (which would appear to be where you got your example).

One can use withTaskGroup(of:returning:body:) to create a task group to run tasks in parallel, but then collate all the results together at the end.

E.g. here is an example that creates child tasks that return a tuple of “name” and ”image”, and the group returns a combined dictionary of those name strings with their associated image values:

func downloadImages(names: [String]) async -> [String: UIImage] {
await withTaskGroup(
of: (String, UIImage).self,
returning: [String: UIImage].self
) { [self] group in
for name in names {
group.addTask { await (name, downloadPhoto(named: name)) }
}

var images: [String: UIImage] = [:]

for await result in group {
images[result.0] = result.1
}

return images
}
}

Or, more concisely:

func downloadImages(names: [String]) async -> [String: UIImage] {
await withTaskGroup(of: (String, UIImage).self) { [self] group in
for name in names {
group.addTask { await (name, downloadPhoto(named: name)) }
}

return await group.reduce(into: [:]) { $0[$1.0] = $1.1 }
}
}

They run in parallel:

Sample Image

But you can extract them from the dictionary of results:

let stooges = ["moe", "larry", "curly"]
let images = await downloadImages(names: stooges)

imageView1.image = images["moe"]
imageView2.image = images["larry"]
imageView3.image = images["curly"]

Or if you want an array sorted in the original order, just build an array from the dictionary:

func downloadImages(names: [String]) async -> [UIImage] {
await withTaskGroup(of: (String, UIImage).self) { [self] group in
for name in names {
group.addTask { await (name, downloadPhoto(named: name)) }
}

let dictionary = await group.reduce(into: [:]) { $0[$1.0] = $1.1 }
return names.compactMap { dictionary[$0] }
}
}

How to read only first part of a file in Swift?

Use the FileHandle API, which has read(upToCount:):

let handle = try FileHandle(forReadingFrom: someURL)
let first16Bytes: Data? = try handle.read(upToCount: 16)
try handle.close()

Alternatively, you can read the file asynchronously, in which case you can control how many bytes you read by moving the iterator forward (calling AsyncIterator.next), or use prefix to get the first bytes.

Swift 5.5 async let - error: expression is 'async' but is not marked with 'await'

Building as a macOS command line project (Xcode: File -> New -> Project, then select "Command Line Tool" from the macOS tab), the code works perfectly. (This was a suggestion from a response in the Apple Developer Forums.)

I've added a single sleep(10) to the end so that the command line tool doesn't exit before the async calls finish:

import Foundation

print("Hello, world!")

func doIt() async -> String {
let t = TimeInterval.random(in: 0.25 ... 2.0)
Thread.sleep(forTimeInterval: t)
return String("\(Double.random(in: 0...1000))")
}

async {
async let a = doIt()
async let b = doIt()
async let c = doIt()
async let d = doIt()

let results = await [a, b, c, d]
for result in results {
print(" \(result)")
}
}
sleep(10)

This produces the expected sort of console output (Note: the actual values will differ on every run)*:

Hello, World!
415.407747869283
574.28639828183
689.4706625185836
385.56539085197113
Program ended with exit code: 0

How can I use async/await with SwiftUI in Swift 5.5?

As per new informations in WWDC session Meet async/await in Swift
WWDC21, at 23m:28s this is now done using:

Task {
someState = await someAsyncFunction()
}

Screenshot from the session.

Image showing how to use an Async task on a sync context

Note that there are more ways to instantiate a task. This is the most basic. You can use Task.detached as well and both ways can get a priority argument.
Check both the Task docs and the session
Check Explore structured concurrency in Swift WWDC21 at around 23:05 (I recommend the whole session!) for more info.



Related Topics



Leave a reply



Submit