I'm determined to fully understand why this isn't causing a reference cycle. And in general what is happening at each stage of memory management here.
I have the following setup:
struct PresenterView: View {
@State private var isPresented = false
var body: some View {
Text("Show")
.sheet(isPresented: $isPresented) {
DataList()
}
.onTapGesture {
isPresented = true
}
}
}
struct DataList: View {
@StateObject private var viewModel = DataListViewModel()
var body: some View {
NavigationView {
List(viewModel.itemViewModels, id: \.self) { itemViewModel in
Text(itemViewModel.displayText)
}.onAppear {
viewModel.fetchData()
}.navigationBarTitle("Items")
}
}
}
class DataListViewModel: ObservableObject {
private let webService = WebService()
@Published var itemViewModels = [ItemViewModel]()
private var cancellable: AnyCancellable?
func fetchData() {
cancellable = webService.fetchData().sink(receiveCompletion: { _ in
//...
}, receiveValue: { dataContainer in
self.itemViewModels = dataContainer.data.items.map { ItemViewModel($0) }
})
}
deinit {
print("deinit")
}
}
final class WebService {
var components: URLComponents {
//...
return components
}
func fetchData() -> AnyPublisher<DataContainer, Error> {
return URLSession.shared.dataTaskPublisher(for: components.url!)
.map { $0.data }
.decode(type: DataContainer.self, decoder: JSONDecoder())
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
}
So when I create a PresenterView and then dismiss it I get a successful deinit print.
However I don't understand why they is no reference cycle here. DataListViewModel
has cancellables
which has a subscription that captures self. So DataListViewModel
-> subscription and subscription -> DataListViewModel
. How can deinit
be triggered? In general is there a good approach to understanding whether there is a retain cycle in these kinds of situation?