3

Hi i am getting following errors while iterating over a custom object with for try await:-

  • For-in loop requires '[Photo]' to conform to 'AsyncSequence'
  • Type of expression is ambiguous without more context

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'

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"

3
  • Why are you using a loop in the first place? You seem to return from the function after inspecting the very first item in the array, or you throw an error. Either way, you don't seem to have any intention of going to the second element, ever. Commented Jun 30, 2021 at 12:06
  • @Sweeper i want to return the array of images, i was returning first image for demo purpose. updated my question. What i want to understand is how to apply async sequence on a common scenario different from provided by apple in WWDC. Commented Jun 30, 2021 at 13:37
  • @RajaKishan no removing try doesnt help get rid of those errors. Commented Jun 30, 2021 at 13:38

1 Answer 1

2

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.

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks so much for the awesome explaination. yes you are right, i dont need an async sequence for the photo. I would just need to fetch it asynchronously and may be instead of waiting for all array of images, just return them one by one. I wanted to try the new for await syntax, but the usecase is wrong. thanks.
i still get errors with this answer: "Type 'AsyncPhotoSequence' does not conform to protocol 'AsyncSequence" and "Reference to generic type 'IndexingIterator' requires arguments in <...>"
@AshishPisey Haha 😅 The truth is, I don't actually have Xcode Beta to compile the code, so I expected there to be some silly mistakes, like forgetting to add the generic type parameter for IndexIterator. It seems like I also need the Element type alias in AsyncPhotoSequence. Does it work now?
yes @Sweeper it works now. Thanks a ton. i will post the link to github project with possible different solutions i tried here in comments.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.