视图层次结构中的 SwiftUI 保留周期
SwiftUI retain cycle in view hierarchy
我有以下具有保留周期的视图层次结构,这是我可以重现问题的最简单方法。所有视图模型和属性都必须保留,因为它们在原始解决方案中是必需的:
import SwiftUI
struct MainView: View {
@StateObject var viewModel = MainViewModel()
var body: some View {
NavigationView { [weak viewModel] in
VStack {
Button("StartCooking") {
viewModel?.show()
}
if viewModel?.isShowingContainerView == true {
ContainerView()
}
Button("StopCooking") {
viewModel?.hide()
}
}
}
.navigationViewStyle(.stack)
}
}
final class MainViewModel: ObservableObject {
@Published var isShowingContainerView = false
func show() {
isShowingContainerView = true
}
func hide() {
isShowingContainerView = false
}
}
struct ContainerView: View {
@Namespace var namespace
var body: some View {
VStack {
SubView(
namespace: namespace
)
}
}
}
struct SubView: View {
@StateObject var viewModel = SubViewModel()
var namespace: Namespace.ID
var body: some View {
Text("5 min")
.matchedGeometryEffect(id: UUID().uuidString, in: namespace)
.onTapGesture {
foo()
}
}
private func foo() {}
}
final class SubViewModel: ObservableObject {}
如果我 运行 应用程序,点击 StartCooking
,而不是 StopCooking
并检查内存图,我仍然看到 SubViewModel
的实例,这意味着此代码中存在泄漏。
如果我删除:
NavigationView
或
- 来自
ContainerView
或 的 VStack
matchedGeometryEffect
或
tapGesture
保留循环已解决。不幸的是我需要所有这些。您能看出问题可能是什么吗?如何解决?
看起来像是一个 SwiftUI 错误。一种可能的解决方法(如果 sub-view 是一个或有限的集合)是使用视图模型工厂来提供实例。
这是一个视图的示例:
struct SubView: View {
@StateObject var viewModel = SubViewModel.shared // single instance !!
// .. other code
}
final class SubViewModel: ObservableObject {
static var shared = SubViewModel() // << this !!
}
我可以通过在 SubViewModel
和 运行 中使每个 属性 可选成为 SubView
消失时的函数来解决它,这使得它们 nil
。 SubViewModel
仍然留在内存中,但不会占用那么多 space。
有趣的是,我什至尝试将视图模型设为可选,并在视图消失时将其设为零,但它仍然留在内存中。
我有以下具有保留周期的视图层次结构,这是我可以重现问题的最简单方法。所有视图模型和属性都必须保留,因为它们在原始解决方案中是必需的:
import SwiftUI
struct MainView: View {
@StateObject var viewModel = MainViewModel()
var body: some View {
NavigationView { [weak viewModel] in
VStack {
Button("StartCooking") {
viewModel?.show()
}
if viewModel?.isShowingContainerView == true {
ContainerView()
}
Button("StopCooking") {
viewModel?.hide()
}
}
}
.navigationViewStyle(.stack)
}
}
final class MainViewModel: ObservableObject {
@Published var isShowingContainerView = false
func show() {
isShowingContainerView = true
}
func hide() {
isShowingContainerView = false
}
}
struct ContainerView: View {
@Namespace var namespace
var body: some View {
VStack {
SubView(
namespace: namespace
)
}
}
}
struct SubView: View {
@StateObject var viewModel = SubViewModel()
var namespace: Namespace.ID
var body: some View {
Text("5 min")
.matchedGeometryEffect(id: UUID().uuidString, in: namespace)
.onTapGesture {
foo()
}
}
private func foo() {}
}
final class SubViewModel: ObservableObject {}
如果我 运行 应用程序,点击 StartCooking
,而不是 StopCooking
并检查内存图,我仍然看到 SubViewModel
的实例,这意味着此代码中存在泄漏。
如果我删除:
NavigationView
或- 来自
ContainerView
或 的 VStack
matchedGeometryEffect
或tapGesture
保留循环已解决。不幸的是我需要所有这些。您能看出问题可能是什么吗?如何解决?
看起来像是一个 SwiftUI 错误。一种可能的解决方法(如果 sub-view 是一个或有限的集合)是使用视图模型工厂来提供实例。
这是一个视图的示例:
struct SubView: View {
@StateObject var viewModel = SubViewModel.shared // single instance !!
// .. other code
}
final class SubViewModel: ObservableObject {
static var shared = SubViewModel() // << this !!
}
我可以通过在 SubViewModel
和 运行 中使每个 属性 可选成为 SubView
消失时的函数来解决它,这使得它们 nil
。 SubViewModel
仍然留在内存中,但不会占用那么多 space。
有趣的是,我什至尝试将视图模型设为可选,并在视图消失时将其设为零,但它仍然留在内存中。