为何向Sheet View构造器传入ViewModel会导致内存泄漏?
问题:Sheet ViewModel 传入构造器后关闭Sheet时无法执行deinit
我的目标是遵循依赖倒置原则,让SheetView依赖于Sheet ViewModel的协议。但遇到问题:将ViewModel传入Sheet View的构造器后,关闭Sheet View时ViewModel不会执行deinit。
代码示例:
.sheet(isPresented: $viewModel.isSheetPresented) { let params = SheetViewModelParams( initialCount: 0, initialViewType: .List ) /** - Bug: ViewModel will not deinit. Use .init(params:) */ // let viewModel = SheetViewModel(params: params) // SheetView(viewModel: viewModel) SheetView(params: params) }
测试要求
- 仅在Sheet View展示时初始化Sheet ViewModel;
- 关闭Sheet View时销毁Sheet ViewModel;
- 展示Sheet List View时初始化Sheet List ViewModel;
- 关闭Sheet View或展示Sheet New View时销毁Sheet List ViewModel。
核心代码文件
ContentWithoutStateView.swift
import SwiftUI struct ContentWithoutStateView: View { @State var renderCount: Int = 0 @StateObject var viewModel: ContentWithoutStateViewModel init(params: ContentWithoutStateViewModelParams) { self._viewModel = StateObject( wrappedValue: ContentWithoutStateViewModel.shared(params: params) ) } var body: some View { VStack(spacing: 8) { Text("Content Without State") switch viewModel.onSubmitStatus { case .initial: Button { Task { await viewModel.onSubmit() } } label: { Text("Submit") } .onAppear { print("\(type(of: self)) initial") } case .loading: ProgressView() .onAppear { print("\(type(of: self)) loading") } case .success: Text("Success") .onAppear { print("\(type(of: self)) Success") } case .failure: Text("Failure") .onAppear { print("\(type(of: self)) Failure") } } CountComponent( count: $viewModel.count, onDecrement: { viewModel.count -= 1 }, onIncrement: { viewModel.count += 1 } ) Button { viewModel.isSheetPresented = true } label: { Text("show the sheet") } } .padding(.all, 16) .border(.secondary) .onReceive(viewModel.objectWillChange, perform: { _ in renderCount += 1 print("\(type(of: self)) viewModel will change. count: \(renderCount)") }) .sheet(isPresented: $viewModel.isSheetPresented) { let params = SheetViewModelParams( initialCount: 0, initialViewType: .List ) /** - Bug: ViewModel will not deinit. Use .init(params:) */ // let viewModel = SheetViewModel(params: params) // SheetView(viewModel: viewModel) SheetView(params: params) } } } #Preview { let params = ContentWithoutStateViewModelParams() return ContentWithoutStateView(params: params) }
OnSubmitStatus.swift
enum OnSubmitStatus { case initial case loading case success case failure }
ContentWithoutStateViewModelParams.swift
struct ContentWithoutStateViewModelParams { let initialCount: Int let initialOnSubmitStatus: OnSubmitStatus let initialIsSheetPresented: Bool init( initialCount: Int = 0, initialOnSubmitStatus: OnSubmitStatus = .initial, initialIsSheetPresented: Bool = false ) { self.initialCount = initialCount self.initialOnSubmitStatus = initialOnSubmitStatus self.initialIsSheetPresented = initialIsSheetPresented } }
ContentWithoutStateViewModel.swift
final class ContentWithoutStateViewModel: ObservableObject { @Published var count: Int @Published var onSubmitStatus: OnSubmitStatus @Published var isSheetPresented: Bool init(params: ContentWithoutStateViewModelParams) { self.count = params.initialCount self.onSubmitStatus = params.initialOnSubmitStatus self.isSheetPresented = params.initialIsSheetPresented print("\(type(of: self)) \(#function)") } deinit { print("\(type(of: self)) \(#function)") } func fetchContent() async -> Result<Bool, Error> { sleep(1) return .success(true) } @MainActor func onSubmit() async { onSubmitStatus = .loading let result = await fetchContent() result.fold { success in count += 1 onSubmitStatus = .success } errorTransform: { failure in count -= 1 onSubmitStatus = .failure } } }
ContentWithoutStateViewModel+Shared.swift
extension ContentWithoutStateViewModel { static func shared(params: ContentWithoutStateViewModelParams) -> ContentWithoutStateViewModel { var temp: ContentWithoutStateViewModel if _shared == nil { temp = ContentWithoutStateViewModel(params: params) _shared = temp } return _shared! } static weak var _shared: ContentWithoutStateViewModel? }
Sheet 模块代码
SheetView.swift
struct SheetView: View { @State var renderCount: Int = 0 @StateObject var viewModel: SheetViewModel init(params: SheetViewModelParams) { self._viewModel = StateObject( wrappedValue: SheetViewModel.shared(params: params) ) } @available( *, deprecated, message: "Bug: ViewModel will not deinit when Sheet is dismissed. use .init(params:)" ) init(viewModel: SheetViewModel) { self._viewModel = StateObject(wrappedValue: viewModel) } var body: some View { VStack(spacing: 8) { Text("Sheet") CountComponent( count: $viewModel.count, onDecrement: { viewModel.count -= 1 }, onIncrement: { viewModel.count += 1 } ) Button { viewModel.selectedViewType = .List } label: { Text("show the sheet list") } Button { viewModel.selectedViewType = .New } label: { Text("show the sheet new") } switch viewModel.selectedViewType { case .List: let params = SheetListViewModelParams(initialCount: 0) SheetListView(params: params) case .New: let params = SheetNewViewModelParams(initialCount: 0) SheetNewView(params: params) } } .padding(.all, 16) .border(.secondary) .onReceive(viewModel.objectWillChange, perform: { _ in renderCount += 1 print("\(type(of: self)) viewModel will change. count: \(renderCount)") }) } } #Preview { let params = SheetViewModelParams( initialCount: 0, initialViewType: .List ) return SheetView(params: params) }
SheetViewType.swift
enum SheetViewType { case List case New }
SheetViewModelParams.swift
struct SheetViewModelParams { let initialCount: Int let initialViewType: SheetViewType }
SheetViewModel.swift
import Foundation final class SheetViewModel: ObservableObject { let id: UUID = UUID() @Published var count: Int @Published var selectedViewType: SheetViewType init( params: SheetViewModelParams ) { self.count = params.initialCount self.selectedViewType = params.initialViewType print("\(type(of: self)) \(#function) \(id)") } deinit { print("\(type(of: self)) \(#function) \(id)") } }
SheetViewModel+Shared.swift
extension SheetViewModel { static func shared(params: SheetViewModelParams) -> SheetViewModel { var temp: SheetViewModel if _shared == nil { temp = SheetViewModel(params: params) _shared = temp } return _shared! } private static weak var _shared: SheetViewModel? }
Sheet List 模块代码
SheetListView.swift
struct SheetListView: View { @State var renderCount: Int = 0 @StateObject var viewModel: SheetListViewModel init(params: SheetListViewModelParams) { self._viewModel = StateObject( wrappedValue: SheetListViewModel.shared(params: params) ) } var body: some View { VStack(spacing: 8) { Text("Sheet List") CountComponent( count: $viewModel.count, onDecrement: { viewModel.count -= 1 }, onIncrement: { viewModel.count += 1 } ) } .padding(.all, 16) .border(.secondary) .onReceive(viewModel.objectWillChange, perform: { _ in renderCount += 1 print("\(type(of: self)) viewModel will change. count: \(renderCount)") }) } } #Preview { let params = SheetListViewModelParams(initialCount: 0) return SheetListView(params: params) }
SheetListViewModelParams.swift
import Foundation struct SheetListViewModelParams { let initialCount: Int }
SheetListViewModel.swift
import Foundation final class SheetListViewModel: ObservableObject { let id = UUID() @Published var count: Int init(params: SheetListViewModelParams) { self.count = params.initialCount print("\(type(of: self)) \(#function) \(id)") } deinit { print("\(type(of: self)) \(#function) \(id)") } }
SheetListViewModel+Shared.swift
extension SheetListViewModel { static func shared(params: SheetListViewModelParams) -> SheetListViewModel { var temp: SheetListViewModel if _shared == nil { temp = SheetListViewModel(params: params) _shared = temp } return _shared! } private static weak var _shared: SheetListViewModel? }
Sheet New 模块代码
SheetNewView.swift
import SwiftUI struct SheetNewView: View { @State var renderCount = 0 @StateObject var viewModel: SheetNewViewModel init(params: SheetNewViewModelParams) { self._viewModel = StateObject( wrappedValue: SheetNewViewModel.shared(params: params) ) } var body: some View { VStack(spacing: 8) { Text("Sheet New") CountComponent( count: $viewModel.count, onDecrement: { viewModel.count -= 1 }, onIncrement: { viewModel.count += 1 } ) } .padding(.all, 16) .border(.secondary) .onReceive(viewModel.objectWillChange, perform: { _ in renderCount += 1 print("\(type(of: self)) viewModel will change. count: \(renderCount)") }) } } #Preview { let params = SheetNewViewModelParams(initialCount: 0) return SheetNewView(params: params) }
SheetNewViewModelParams.swift
struct SheetNewViewModelParams { let initialCount: Int }
SheetNewViewModel.swift
import Foundation final class SheetNewViewModel: ObservableObject { let id: UUID = UUID() @Published var count: Int init(params: SheetNewViewModelParams) { self.count = params.initialCount print("\(type(of: self)) \(#function) \(id)") } deinit { print("\(type(of: self)) \(#function) \(id)") } }
SheetNewViewModel+Shared.swift
extension SheetNewViewModel { static func shared(params: SheetNewViewModelParams) -> SheetNewViewModel { var temp: SheetNewViewModel if _shared == nil { temp = SheetNewViewModel(params: params) _shared = temp } return _shared! } private static weak var _shared: SheetNewViewModel! }
内容的提问来源于stack exchange,提问作者Jason Rich Darmawan
相关产品推荐
相关产品推荐

