Hi i am getting following errors while iterating over a custom object with for try await:-
Custom object:-
enum FetchError: Error {
case badImage
case badRequest
case invalidImageURL
case noURL
case failedToFetchImage
}
struct Photo: Codable {
let albumId: Int
let id: Int
let title: String
let urlPath: String
let thumbnailUrl: String
}
Working code for fetching 1st image:-
Class ViewController: UIViewController {
func fetchAsyncImage(request:URLRequest) async throws -> UIImage {
let (data, response) = try await URLSession.shared.data(for: request)
guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw FetchError.badRequest }
let photos = try JSONDecoder().decode([Photo].self, from: data)
guard let imagePath = photos.first?.urlPath,
let imageURL = URL.init(string: imagePath) else { throw FetchError.noURL }
let (imageData, imageResponse) = try await URLSession.shared.data(from: imageURL)
guard (imageResponse as? HTTPURLResponse)?.statusCode == 200 else { throw FetchError.invalidImageURL }
guard let firstImage = UIImage(data: imageData) else { throw FetchError.badImage }
return firstImage
}
Issue while performing async sequence on Photo object
func fetchAsyncImage(request:URLRequest) async throws -> [UIImage] {
let (data, response) = try await URLSession.shared.data(for: request)
guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw FetchError.badRequest }
let photos = try JSONDecoder().decode([Photo].self, from: data)
guard let imagePath = photos.first?.urlPath,
let imageURL = URL.init(string: imagePath) else { throw FetchError.noURL }
var imageArr:[UIImage] = []
for await photo in photos {
guard let imagePath = photo.urlPath,
let imageURL = URL.init(string: imagePath) else { throw FetchError.noURL }
do {
let (imageData, imageResponse) = try await URLSession.shared.data(from: imageURL)
guard (imageResponse as? HTTPURLResponse)?.statusCode == 200 else { throw FetchError.invalidImageURL }
guard let image = UIImage(data: imageData) else { throw FetchError.badImage }
imageArr.append(image)
} catch {
throw FetchError.failedToFetchImage
}
}
return imageArr
}
Getting this error: -
![For-in loop requires '[Photo]' to conform to 'AsyncSequence'](https://i.sstatic.net/c4a9M.png)
What i tried for implementing async sequence:-
struct Photo: Codable, AsyncSequence {
typealias Element = URL
let albumId: Int
let id: Int
let title: String
let urlPath: String
let thumbnailUrl: String
struct AsyncIterator: AsyncIteratorProtocol {
let urlPath: String
mutating func next() async throws -> URL? {
do {
guard let imageURL = URL.init(string: urlPath) else { throw FetchError.noURL }
return imageURL
} catch {
throw FetchError.invalidImageURL
}
}
}
func makeAsyncIterator() -> AsyncIterator {
AsyncIterator(urlPath: urlPath)
}
}
i am not sure how to iterate over photo objects with "for try await"
photos is not an async sequence. An async sequence is a sequence that returns its next elements asynchronously. However, photos is an array of Photos, an array, everything is stored in memory. To fetch the next element, you just access the next thing in memory. There's nothing async about it. In this case, it is the processing (fetching the UIImage) of the array elements that involves an asynchronous operation, not the “fetching the next element” part, so you should use await in the loop body, which you correctly did.
A regular for loop will do the job:
for photo in photos {
guard let imagePath = photo.urlPath,
let imageURL = URL.init(string: imagePath) else { throw FetchError.noURL }
do {
let (imageData, imageResponse) = try await URLSession.shared.data(from: imageURL)
guard (imageResponse as? HTTPURLResponse)?.statusCode == 200 else { throw FetchError.invalidImageURL }
guard let image = UIImage(data: imageData) else { throw FetchError.badImage }
imageArr.append(image)
} catch {
throw FetchError.failedToFetchImage
}
}
The loop will fetch the first image, wait for it to finish, then fetch the second, wait for that to finish, and so on. IMO, you could just fetch them all at the same time with a task group, unless you have some special requirements that I'm not aware of.
Anyway, it is also incorrect to conform Photo to AsyncSequence. After all, one Photo isn't a sequence. What you can do instead, if you really want to use AsyncSequence, is to create a AsyncPhotoSequence struct that takes a [Photo]. Note that this is a sequence of UIImage.
struct AsyncPhotoSequence: AsyncSequence {
let photos: [Photo]
typealias Element = UIImage
struct AsyncPhotoIterator: AsyncIteratorProtocol {
var arrayIterator: IndexingIterator<[Photo]>
mutating func next() async throws -> UIImage? {
guard let photo = arrayIterator.next() else { return nil }
// Here you are supposed to check for cancellation,
// read more here:
// https://developer.apple.com/documentation/swift/asynciteratorprotocol#3840267
// copied from your loop body
guard let imagePath = photo.urlPath,
let imageURL = URL.init(string: imagePath) else { throw FetchError.noURL }
do {
let (imageData, imageResponse) = try await URLSession.shared.data(from: imageURL)
guard (imageResponse as? HTTPURLResponse)?.statusCode == 200 else { throw FetchError.invalidImageURL }
guard let image = UIImage(data: imageData) else { throw FetchError.badImage }
return image
} catch {
throw FetchError.failedToFetchImage
}
}
}
func makeAsyncIterator() -> AsyncPhotoIterator {
AsyncPhotoIterator(arrayIterator: photos.makeIterator())
}
}
Then you can use for try await with AsyncPhotoSequence:
for try await image in AsyncPhotoSequence(photos: photos) {
imageArr.append(image)
}
But I personally wouldn't go to that trouble.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With