SwiftUI .task does not update its references on view update

Summary

The issue at hand is with SwiftUI’s .task modifier, which does not update its references when the view is updated. This is observed in a sample code where a DoubleGenerator sequence is used to print numbers in a TestView. When the id is updated by clicking the “update id by 1” button, the print statement in the .task modifier is not executed.

Root Cause

The root cause of this issue is due to the way SwiftUI handles the .task modifier. When the view is updated, a new instance of the view is created, and the .task modifier is not updated to reference the new instance. This results in the .task modifier still referencing the old instance, which is no longer valid.

Why This Happens in Real Systems

This issue can occur in real systems when using SwiftUI’s .task modifier to perform asynchronous tasks. The causes of this issue include:

  • The view is updated, causing a new instance to be created
  • The .task modifier is not updated to reference the new instance
  • The old instance is no longer valid, causing the task to fail

Real-World Impact

The impact of this issue can be significant, including:

  • Asynchronous tasks not being executed as expected
  • Data not being updated correctly
  • The app not responding as expected to user input

Example or Code

struct TestView: View {
    var sequence = DoubleGenerator()
    let id: Int
    var body: some View {
        VStack {
            Button {
                sequence.next()
            } label: {
                Text("print next number").background(content: { Color.green })
            }
            Text("current id is \(id)")
        }
        .task {
            for await number in sequence.stream {
                print("next number is \(number)")
            }
        }
    }
}

How Senior Engineers Fix It

Senior engineers can fix this issue by using SwiftUI’s .onChange modifier to update the .task modifier when the view is updated. This can be done by:

  • Using .onChange to detect when the view is updated
  • Updating the .task modifier to reference the new instance
  • Ensuring that the .task modifier is properly cancelled and restarted when the view is updated

Why Juniors Miss It

Juniors may miss this issue because they:

  • Are not familiar with SwiftUI’s .task modifier and its limitations
  • Do not understand how SwiftUI handles view updates and instance creation
  • Do not test their code thoroughly to catch this type of issue
  • Are not aware of the importance of using .onChange to update the .task modifier when the view is updated. Key takeaways include understanding SwiftUI’s .task modifier, view updates, and instance creation, as well as using .onChange to update the .task modifier.