我在代码中遇到了非常奇怪的内存泄漏。它主要是由使用@escaping闭包和嵌套这个闭包引起的。我创建了一个最小的可重复的例子来演示这种内存泄漏。
import SwiftUI
struct IdentifiableAction: Identifiable, Hashable {
public let id: UUID
public let action: () -> Void
public init(action: @escaping () -> Void, id: UUID = .init()) {
self.id = id
self.action = action
}
public func callAsFunction() {
action()
}
public static func == (lhs: IdentifiableAction, rhs: IdentifiableAction) -> Bool {
lhs.id == rhs.id
}
public func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
extension View {
func check(action: Binding<IdentifiableAction?>, label: String) -> some View {
self
.onChange(of: action.wrappedValue) { _ in
guard action.wrappedValue != nil else { return }
print("[DEBUG] Check action \(label)")
action.wrappedValue?()
}
}
}
struct ContentView: View {
var body: some View {
TabView {
NavigationView {
VStack {
NavigationLink("DetailView") {
DetailView()
}
}
.padding()
}
.tabItem { Text("Tab 1") }
Text("Page 2")
.tabItem { Text("Tab 2") }
Text("Page 3")
.tabItem { Text("Tab 3") }
}
}
}
class DetailViewModel: ObservableObject {
init() {
print("[DEBUG]: init DetailViewModel")
}
deinit {
print("[DEBUG]: deinit DetailViewModel")
}
func action() {
print("[DEBUG]: view model action")
}
}
struct DetailView: View {
@State private var actionCheck1: IdentifiableAction?
@State private var actionCheck2: IdentifiableAction?
@State private var actionCheck3: IdentifiableAction?
@StateObject private var viewModel = DetailViewModel()
var body: some View {
VStack {
Text("Detail view!")
Button("Execute checked action") {
actionCheck1 = IdentifiableAction {
actionCheck2 = IdentifiableAction {
actionCheck3 = IdentifiableAction {
viewModel.action()
}
}
}
}
}
.padding()
.check(action: $actionCheck1, label: "1")
.check(action: $actionCheck2, label: "2")
.check(action: $actionCheck3, label: "3")
}
}
字符串
一般来说,我希望在执行一些“检查”后执行操作。这里的检查是简单的打印语句,通过在应用程序中,它可以显示一些视图,对话框,警报与不同的条件。但为了保持示例简单和最小化,我只是打印有关检查完成的语句,然后在视图模型中执行操作。如下所示:
[DEBUG] Check action 1
[DEBUG] Check action 2
[DEBUG] Check action 3
[DEBUG]: view model action
型
我希望每个检查都像视图修改器或视图扩展上的函数(有点类似于sheet(item: $presentedItem)
。所以我有视图扩展函数.check(action: $actionCheck1)
为了能够使用action作为这样的触发器,我需要将每个action Package 在IdentifiableAction结构中,该结构实现了Identifiable协议和Hashable协议,每次创建action时,我都会为每个action添加唯一的UUID。
然后我这样使用这个视图修改器:
.check(action: $actionCheck1, label: "1")
.check(action: $actionCheck2, label: "2")
.check(action: $actionCheck3, label: "3")
型
好了,现在执行的动作是由多个检查被拦截的动作(IdentifiableAction),必须执行和传递执行适当的动作viewModel。我做的动作:
Button("Execute checked action") {
actionCheck1 = IdentifiableAction {
actionCheck2 = IdentifiableAction {
actionCheck3 = IdentifiableAction {
viewModel.action()
}
}
}
}
型
上面的代码会导致内存泄漏。理论上我们在结构体的View中,我们使用结构体的IdentifiableAction,唯一的引用类型是DetailsViewModel类和传递给IdentifiableAction的闭包。
所以内存泄漏的问题是在IdentifiableActions中使用的嵌套闭包。
我已经厌倦了使用像[weak viewModel]
这样的捕获列表,但它只适用于单个IdentifiableAction。
Button("Execute checked action") {
actionCheck1 = IdentifiableAction { [weak viewModel] in
viewModel?.action()
}
}
型
所以上面的代码防止了内存泄漏。
但是在嵌套闭包中添加额外的[weakviewModel]捕获列表并没有改变任何东西,内存泄漏仍然存在。
Button("Execute checked action") {
actionCheck1 = IdentifiableAction { [weak viewModel] in
actionCheck2 = IdentifiableAction { [weak viewModel] in
actionCheck3 = IdentifiableAction { [weak viewModel] in
viewModel?.action()
}
}
}
}
型
1条答案
按热度按时间ct3nt3jp1#
好了,我已经解决了这个问题。问题是我在视图修饰符中执行action之后没有将其设置为nil。所以这个action总是保存在@State属性 Package 器中,并且也保存了它们自己。在重置than为nil之后,所有内容都正确地与视图模型本身一起释放。我也不需要在捕获列表中使用任何weak。只是按照预期工作。