Я уже знаю о концепции сильной/слабой ссылки в swift.
однако после запуска следующего кода и нажатия кнопки (и закрытия экрана) TestViewModel остался в памяти!
Я ожидал, что использования [weak viewmodel] будет достаточно, чтобы предотвратить это.
во втором примере мне удалось это исправить - но я не понимаю, почему это сработало
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 кскод 14.2
@malhal спасибо за вклад, но почему использование объекта модели представления неверно? и как это связано с проблемой?
Это потому, что вы упоминаете проблемы с памятью, типичные для объектов, а StateObject не предназначен для объекта модели представления. Это когда вам нужен ссылочный тип в состоянии. Поскольку вы используете async/await, вам не нужен объект, лучше всего придерживаться типов значений, таких как встроенная структура View
для ваших данных представления. Используйте модификатор .task
структуры View, чтобы получить асинхронный контекст.
Я понимаю, что вы говорите, но учтите, что это очень узкий пример, который не представляет мой реальный код. Я просто пытаюсь понять быстрое поведение под капотом по отношению к слабым ссылкам. и в связи с этим, я думаю, что есть много примеров моделей просмотра, использующих объект состояния.
@malhal кстати, я попробовал то же самое без stateObject - изменив его на обычный класс, и проблема все еще сохраняется. так что вы все еще можете ответить на исходный вопрос - почему [weak viewmodel] в первом примере не работает
Да обычный класс не подойдет. В SwiftUI используйте такие структуры, как структура View с @State со структурой или значением.
Ваша первая версия:
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 удерживает ваш @StateObject
до тех пор, пока TestScreen
является частью иерархии представлений, то есть до тех пор, пока Button
является частью иерархии представлений. Таким образом, SwiftUI поддерживает сильную ссылку на ваш TestViewModel
до тех пор, пока не вызовет действие вашего Button
. Таким образом, в вашей первой версии ваша слабая ссылка viewmodel
внутри действия Button
никогда не будет нулевой. Следовательно, vm
никогда не будет нулевым, action
никогда не будет нулевым, а action
всегда будет строго ссылаться на TestViewModel
.
Ваша вторая версия:
testStruct.saveActionGlobaly(onAsyncAction: { await viewmodel?.someAsyncAction() } )
сохраняет слабость переменной viewmodel
. Он создает сильную ссылку на TestViewModel
лишь на мгновение при каждом вызове и отбрасывает сильную ссылку сразу после возврата someAsyncAction
.
во-первых, vm и action не являются одним и тем же типом, так что вы меня уже тут же запутали (; во-вторых, почему ссылка на слабую модель представления должна быть нулевой, чтобы она работала? почему действие «настаивает» на сохранении сильной ссылки? нужно немного больше объяснений (или я должен где-то просмотреть тему?)
«vm и action не одного типа» — извините, это ошибка. Я исправил это. Что касается действия, «настаивающего» на сохранении строгой ссылки, именно так работает Swift, когда вы говорите viewmodel?.someAsyncAction
. Вы либо получаете nil, либо получаете замыкание, которое содержит сильную ссылку, в зависимости от того, является ли слабая ссылка nil в момент вычисления выражения. Если вы хотите сохранить слабость ссылки, вы должны явно создать замыкание, как вы это делаете во второй версии.
эй, Роб, все еще ломаю голову над этим... Я заменил stateObject обычным классом - проблема все еще сохраняется (первый пример), оставляя несколько моделей просмотра после закрытия экрана. хотя вам удалось разрушить различия между двумя примерами, я не могу полностью понять, почему модель представления сохраняется как сильная ссылка в первом примере - почему действие должно быть нулевым в качестве предварительного условия? слабая ссылка защищает вас от нуля в будущем, а не до
также я нашел этот пост связанным - stackoverflow.com/questions/50582360/…. особенно последний ответ.
Используя ответ Роба и прочитав дополнительное чтение, я думаю, мне удалось пролить больше света на это (по крайней мере, для меня, поскольку ответ Роба, конечно, правильный):
во-первых, концепция слабой ссылки - это игра компилятора, то есть компилятор просматривает первый пример и переводит его в: (как описано Робом)
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)
что имеет смысл...
недостающей частью для меня было понимание следующего предложения Роба:
действие всегда будет иметь сильную ссылку на TestViewModel
Итак, есть еще один шаг, на котором компилятор переводит action
как замыкание (очень абстрактно):
{
viewmodel.action // implicit viewmodel
}
и передает его onAsyncAction
аргументу.
другими словами, возвращаемое замыкание из оценки action
содержит другую неявную ссылку на модель представления. компилятор не может сделать вывод, что явная и неявная модели представления связаны, поэтому слабость не применяется к более поздним
Чтобы эффективно использовать SwiftUI, вам нужно научиться использовать структуру View для ваших данных представления вместо объекта модели представления.