I would like to understand the proper usage of Task { } in the following SwiftUI code. My goal is to get a fundamental understand to avoid memory leaks.
This is the sample code for the SwiftUI part:
struct MyView: View {
@ObservedObject var viewModel: ViewModel
var body: some View {
VStack {
Text(viewModel.publishedString)
}
.onAppear(perform: {
Task {
viewModel.resetUI()
await viewModel.doSomeAsyncStuff()
}
})
.onDisappear {
Task {
await viewModel.doAnotherAsyncStuff()
viewModel.resetUI()
}
}
}
}
The .onAppear { … } and .onDisappear { … } modifier will call a synchronous and asynchronous function of the ViewModel class. The Text(viewModel.publishedString) is representing a published property (using @Published) that is modified by viewModel.doSomeAsyncStuff() and viewModel.doAnotherAsyncStuff().
Keep in mind that I need to be backwards-compatible to SwiftUI 2.0, which is why I am not using the .task { } modifier.
Can you please confirm, if my understanding is correct with regards to memory leaks and retain cycles for this example:
- During call of
Task { … }, a copy of the MyView type value will be created inside the Task closure - The copy of that struct type value is holding a reference to the
ViewModelclass - The hold reference to the
ViewModelis not allocating new memory (because it is just a pointer, whereas the copy of struct will allocating new memory that is freed up upon completion of the Task closure - Function calls to the ViewModel class and updates of the published
publishedStringproperty inside the Task closure will update the SwiftUI view because all copies of the View struct holding the same reference to the ViewModel class - Completing the Task closure will de-allocate the copy of the View struct
- If the initial MyView value (not the copy inside the Task closure) has to be de-allocated from memory because it is not needed anymore, but the Task closure has not been completed yet, a strong reference is hold to the ViewModel reference only until completion of Task closure. As soon as the Task closures (in
.onAppear { … }and.onDisappear { … }) are completed, all allocated memory for View struct and ViewModel class references are freed up.