0

I have the following struct (View) that uses async/await internally.

struct CachedAsyncImage<I: View, P: View>: View { @State var image: UIImage? let content: (Image) -> I let placeholder: () -> P let url: URL? public init( url: URL?, @ViewBuilder content: @escaping (Image) -> I, @ViewBuilder placeholder: @escaping () -> P ) { self.url = url self.content = content self.placeholder = placeholder } var body: some View { async { guard let url = self.url else { return } let session = URLSession.shared let taskResponse = try? await session.data(from: url, delegate: nil) guard let imageData = taskResponse?.0, let uiImage = UIImage(data: imageData) else { return } image = uiImage } guard let image = self.image else { return AnyView(placeholder()) } return AnyView(content(Image(uiImage: image))) } } 

The only way I made the internal views (I and P types) to swap accordingly was to assign the image property, which had to be marked with the @State property wrapper, from inside the async block. Is this a safe practice from a threading/thread-safety perspective or do I have to implement it in some other way?

4
  • 1
    I don't think initiating URLSession every time the body of the View is rendered is a good idea. Maybe use onAppear on the new .task instead? Commented Jun 17, 2021 at 21:17
  • Yes, that's clear. I'd normally move this away completely since network operations don't really belong into the View. This was meant to be a minimal example. Commented Jun 17, 2021 at 21:19
  • Tasks will be cancelled when the view disappears. As @jnpdx mentioned, task is the new onAppear. Commented Jun 18, 2021 at 5:45
  • It’s best to let the URLSession do your caching via URLCache Commented Jan 1, 2022 at 12:52

2 Answers 2

4

Yes (but it should be done in a .task or .task(id:) modifier not directly in body itself).

You can safely mutate state properties from any thread.

https://developer.apple.com/documentation/swiftui/state

One of the reasons why you should try to use value types like structs instead of objects in Swift.

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

4 Comments

Could you please explain in this case why should I use structs instead of objects in Swift ?
You also need to know about mutating func to use structs
I mean how in this case struct gives us the ability to change the state in the different thread (different from main)
0

This answer is no longer relevant, please check the Malhal's post for an up-to-date answer.


It is not safe to update UI data, from another thread than the main one. And Swift's concurrency model makes no guarantees about the thread on which async code runs.

So the answer to your question is a big NO.

You will need to dispatch the property update on the main thread. And the main actor is the recommended way to do this.

Just add a new function for this

@MainActor func updateImage(to newImage: UIImage) { image = newImage } 

, and call it from the async task:

await updateImage(to: uiImage) 

1 Comment

According to Apple docs you can change a state var safely from any thread, see answer below.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.