英文:
Weak reference doesn't work as expected when passing it as a method reference
问题
I am already aware of the strong/weak reference concept in swift.
yet after running the next code, and tapping on the Button (and dismissing the screen), the TestViewModel stayed in memory!
I was expecting that using [weak viewmodel] will be enough to prevent it.
in the second example I managed to fix it - but I don't understand why it worked
EXAMPLE 2:
I managed to prevent the leak by changing the code this way:
(notice the changes in the callback passed to onAsyncAction)
I don't understand why the second TestScreen managed to apply the weak reference and the first one didn't,
thanks (:
environment:
swift 5
xcode 14.2
英文:
I am already aware of the strong/weak reference concept in swift.
yet after running the next code, and tapping on the Button (and dismissing the screen), the TestViewModel stayed in memory!
I was expecting that using [weak viewmodel] will be enough to prevent it.
in the second example I managed to fix it - but I don't understand why it worked
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
}
}
EXAMPLE 2:
I managed to prevent the leak by changing the code this way:
(notice the changes in the callback passed to 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()
}
}
}
}
}
I dont understand why the second TestScreen managed to apply the weak reference and the first one didn't,
thanks (:
environment:
swift 5
xcode 14.2
答案1
得分: 4
Your first version:
testStruct.saveActionGlobaly(onAsyncAction: viewmodel?.someAsyncAction)
is equivalent to this:
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在整个TestScreen
存在于视图层次结构中的时间内都会保持对@StateObject
的强引用,这与Button
存在于视图层次结构的时间相同。因此,SwiftUI会在调用Button
的操作之后仍然保持对TestViewModel
的强引用。因此,在第一个版本中,Button
操作内的弱引用viewmodel
永远不会为nil。因此,vm
永远不会为nil,action
永远不会为nil,并且action
将始终对TestViewModel
保持强引用。
Your second version:
testStruct.saveActionGlobaly(onAsyncAction: { await viewmodel?.someAsyncAction() })
保持了viewmodel
变量的弱引用性质。每次调用它时,它只会短暂地创建对TestViewModel
的强引用,并在someAsyncAction
返回后立即丢弃强引用。
英文:
Your first version:
testStruct.saveActionGlobaly(onAsyncAction: viewmodel?.someAsyncAction )
is equivalent to this:
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 holds on to your @StateObject
for as long as TestScreen
is part of the view hierarchy, which is as long as the Button
is part of the view hierarchy. So SwiftUI maintains a strong reference to your TestViewModel
until after it has called your Button
's action. So in your first version, your weak viewmodel
reference inside the Button
's action will never be nil. Therefore vm
will never be nil, action
will never be nil, and action
will always have a strong reference to the TestViewModel
.
Your second version:
testStruct.saveActionGlobaly(onAsyncAction: { await viewmodel?.someAsyncAction() } )
preserves the weakness of the viewmodel
variable. It only creates a strong reference to the TestViewModel
momentarily, each time it is invoked, and discards the strong reference immediately after someAsyncAction
returns.
答案2
得分: 0
Using rob's answer and doing some extra reading, I think I managed to shed some more light over this (at least for me, as rob's answer is, of course, correct):
首先,弱引用的概念是一个编译器游戏 - 这意味着编译器在第一个示例上运行并将其转换为:(如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 will always have a strong reference to the TestViewModel
因此,还有另一个步骤,编译器将action
转换为一个类似以下的闭包(非常抽象):
{
viewmodel.action // implicit viewmodel
}
并将其传递给onAsyncAction
参数。
换句话说,从评估action
返回的闭包持有另一个隐式的viewmodel引用。编译器不能得出显式和隐式的viewmodels是相关的,因此隐式引用不适用于后者。
英文:
Using rob's answer and doing some extra reading I think I managed to shed some more light over this (at least for me, as rob's answer is ofcourse correct):
first of all, the weak-reference concept is a compiler game - meaning, the complier runs over the first example and translate it to: (as rob described)
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)
which makes sense...
the missing part for me was understanding the next sentence by rob:
> action will always have a strong reference to the TestViewModel
so, there is another step where the compiler translate action
to be a closure like so (very abstract):
{
viewmodel.action // implicit viewmodel
}
and hands it over to onAsyncAction
argument.
in other words, the returned closure from evaluating action
is holding another implicit viewmodel reference. the compiler can't conclude the explicit and the implicit viewmodels are related, thus the weakness is not applied to the later
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论