弱引用在作为方法引用传递时无法按预期工作

Weak reference doesn't work as expected when passing it as a method reference

提问人:vigdora 提问时间:4/13/2023 最后编辑:vigdora 更新时间:4/18/2023 访问量:453

问:

我已经知道 swift 中的强/弱引用概念。
然而,在运行下一个代码并点击 Button(并关闭屏幕)后,TestViewModel 仍保留在内存中! 我原以为使用 [弱视图模型] 就足以防止它。 在第二个示例中,我设法修复了它 - 但我不明白它为什么有效

import SwiftUI
import Resolver

struct TestScreen: View {
    
    @StateObject var viewmodel = TestViewModel()
    @Injected var testStruct: TestStruct
    @Environment(\.presentationMode) var presentationMode
    
    var body: some View {
        NavigationView {
            
            VStack(spacing: 0) {
                
                Button("go back") { [weak viewmodel] in
                        testStruct.saveActionGlobaly(onAsyncAction:  viewmodel?.someAsyncAction )
                        presentationMode.wrappedValue.dismiss()
                    }
            }
        }
    }
}


import Foundation
import Resolver
import SwiftUI

public class TestStruct {
   var onAsyncAction: (() async throws -> Void)?
    
    public func saveActionGlobaly(onAsyncAction: (() async throws -> Void)?) {
        self.onAsyncAction = onAsyncAction
    } 
}

示例 2:我设法通过以下方式更改代码来防止泄漏:
(请注意传递给 onAsyncAction 的回调中的更改)

import Resolver

struct TestScreen: View {
    
    @StateObject var viewmodel = TestViewModel()
    @Injected var testStruct: TestStruct
    @Environment(\.presentationMode) var presentationMode
    
    var body: some View {
        NavigationView {
            
            VStack(spacing: 0) {
                
                Button("go back") { [weak viewmodel] in
                        testStruct.saveActionGlobaly(onAsyncAction:  { await viewmodel?.someAsyncAction() } )
                        presentationMode.wrappedValue.dismiss()
                    }
            }
        }
    }
}

我不明白为什么第二个 TestScreen 设法应用弱引用而第一个没有, 谢谢 (:

环境: 斯威夫特 5 xcode 14.2的

Swift SwiftUI 内存泄漏 弱引用 保留周期

评论

0赞 malhal 4/14/2023
为了有效地使用 SwiftUI,您需要学会将视图结构体用于视图数据,而不是视图模型对象
0赞 vigdora 4/15/2023
@malhal感谢您的输入,但为什么使用视图模型对象不正确?它与问题有什么关系?
0赞 malhal 4/15/2023
这是因为您提到了典型的对象内存问题,并且 StateObject 不是为视图模型对象设计的。它适用于需要 State 中的引用类型时。由于您使用的是 async/await,因此您不需要对象,因此最好坚持使用值类型,例如视图数据的内置结构。使用 View 结构的修饰符获取异步上下文。View.task
0赞 vigdora 4/15/2023
我明白你在说什么,但考虑到这是一个非常狭窄的例子,它并不代表我的真实代码。我只是想理解与弱引用相关的引擎盖下的快速行为。关于这一点,我认为有很多使用状态对象的视图模型的例子。
0赞 vigdora 4/16/2023
@malhal顺便说一句,我在没有 stateObject 的情况下尝试了相同的操作 - 将其更改为普通类,问题仍然存在。因此,您仍然可以解决原始问题 - 为什么第一个示例中的 [弱 ViewModel] 不起作用

答:

4赞 rob mayoff 4/13/2023 #1

您的第一个版本:

testStruct.saveActionGlobaly(onAsyncAction:  viewmodel?.someAsyncAction )

相当于这样:

let action: (() async throws -> Void)?
if let vm = viewmodel {
    // vm is a strong non-nil reference, so this closure
    // has a strong non-nil reference to a TestViewModel.
    action = vm.someAsyncAction
} else {
    action = nil
}
testStruct.saveActionGlobaly(onAsyncAction: action)

只要是视图层次结构的一部分,SwiftUI 就会保留你,只要它是视图层次结构的一部分。因此,SwiftUI 会一直保持对 your 的强烈引用,直到它调用了 your 的 action。因此,在你的第一个版本中,你在 的动作中的弱引用永远不会为零。因此,永远不会为零,永远不会为零,并且始终对 .@StateObjectTestScreenButtonTestViewModelButtonviewmodelButtonvmactionactionTestViewModel

您的第二个版本:

testStruct.saveActionGlobaly(onAsyncAction:  { await viewmodel?.someAsyncAction() } )

保留变量的弱点。它只会在每次调用时创建对瞬时的强引用,并在返回后立即丢弃强引用。viewmodelTestViewModelsomeAsyncAction

评论

0赞 vigdora 4/14/2023
首先,VM 和 Action 不是同一类型,所以你已经让我感到困惑了(;其次,为什么弱 ViewModel 引用必须为零才能工作?为什么 Action 是“坚持”保留强引用?我需要更多的解释(或者我应该在某个地方回顾这个话题?
1赞 rob mayoff 4/14/2023
“vm 和 action 不是同一类型”——对不起,这是一个错误。我已经修好了。至于“坚持”保留强参考的行动,这正是 Swift 的工作方式,当你说。你要么得到 nil,要么得到一个包含强引用的闭包,这取决于弱引用在计算表达式时是否为 nil。如果希望保留引用的弱点,则必须显式创建闭包,就像在第二个版本中所做的那样。viewmodel?.someAsyncAction
0赞 vigdora 4/16/2023
嘿,罗伯,还在为这个而绞尽脑汁......我将 stateObject 替换为普通类 - 问题仍然存在(第一个示例),在关闭屏幕后留下多个视图模型。尽管您设法分解了两个示例之间的差异,但我无法完全理解为什么在第一个示例中将视图模型保留为强参考 - 为什么作为先决条件,操作必须为零?弱参照可以保护您免于将来成为零,而不是之前
0赞 vigdora 4/16/2023
我也发现这篇文章相关 - stackoverflow.com/questions/50582360/....特别是最后一个答案。
0赞 vigdora 4/16/2023 #2

使用 Rob 的答案并做一些额外的阅读,我想我设法对此进行了更多的说明(至少对我来说,因为 Rob 的答案当然是正确的):
首先,弱引用概念是一个编译器游戏——这意味着,编译器运行第一个示例并将其转换为:(正如 Rob 所描述的)

let action: (() async throws -> Void)?
if let vm = viewmodel {
    // vm is a strong non-nil reference, so this closure
    // has a strong non-nil reference to a TestViewModel.
    action = vm.someAsyncAction
} else {
    action = nil
}
testStruct.saveActionGlobaly(onAsyncAction: action)

这是有道理的......

对我来说,缺少的部分是理解 Rob 的下一句话:

action 将始终具有对 TestViewModel 的强引用

因此,还有另一个步骤,编译器转换为这样的闭包(非常抽象):action

{
   viewmodel.action // implicit viewmodel
}

并将其交给争论。 换言之,从 Evaluating 返回的闭包包含另一个隐式 ViewModel 引用。编译器无法断定显式视图模型和隐式视图模型是相关的,因此该弱点不适用于后者onAsyncActionaction