Слабая ссылка не работает должным образом при передаче ее в качестве ссылки на метод

Я уже знаю о концепции сильной/слабой ссылки в 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

Чтобы эффективно использовать SwiftUI, вам нужно научиться использовать структуру View для ваших данных представления вместо объекта модели представления.

malhal 14.04.2023 16:33

@malhal спасибо за вклад, но почему использование объекта модели представления неверно? и как это связано с проблемой?

vigdora 14.04.2023 18:30

Это потому, что вы упоминаете проблемы с памятью, типичные для объектов, а StateObject не предназначен для объекта модели представления. Это когда вам нужен ссылочный тип в состоянии. Поскольку вы используете async/await, вам не нужен объект, лучше всего придерживаться типов значений, таких как встроенная структура View для ваших данных представления. Используйте модификатор .task структуры View, чтобы получить асинхронный контекст.

malhal 14.04.2023 19:01

Я понимаю, что вы говорите, но учтите, что это очень узкий пример, который не представляет мой реальный код. Я просто пытаюсь понять быстрое поведение под капотом по отношению к слабым ссылкам. и в связи с этим, я думаю, что есть много примеров моделей просмотра, использующих объект состояния.

vigdora 14.04.2023 19:06

@malhal кстати, я попробовал то же самое без stateObject - изменив его на обычный класс, и проблема все еще сохраняется. так что вы все еще можете ответить на исходный вопрос - почему [weak viewmodel] в первом примере не работает

vigdora 16.04.2023 09:52

Да обычный класс не подойдет. В SwiftUI используйте такие структуры, как структура View с @State со структурой или значением.

malhal 16.04.2023 11:46
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
6
174
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Ваша первая версия:

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 не являются одним и тем же типом, так что вы меня уже тут же запутали (; во-вторых, почему ссылка на слабую модель представления должна быть нулевой, чтобы она работала? почему действие «настаивает» на сохранении сильной ссылки? нужно немного больше объяснений (или я должен где-то просмотреть тему?)

vigdora 13.04.2023 18:43

«vm и action не одного типа» — извините, это ошибка. Я исправил это. Что касается действия, «настаивающего» на сохранении строгой ссылки, именно так работает Swift, когда вы говорите viewmodel?.someAsyncAction. Вы либо получаете nil, либо получаете замыкание, которое содержит сильную ссылку, в зависимости от того, является ли слабая ссылка nil в момент вычисления выражения. Если вы хотите сохранить слабость ссылки, вы должны явно создать замыкание, как вы это делаете во второй версии.

rob mayoff 13.04.2023 19:05

эй, Роб, все еще ломаю голову над этим... Я заменил stateObject обычным классом - проблема все еще сохраняется (первый пример), оставляя несколько моделей просмотра после закрытия экрана. хотя вам удалось разрушить различия между двумя примерами, я не могу полностью понять, почему модель представления сохраняется как сильная ссылка в первом примере - почему действие должно быть нулевым в качестве предварительного условия? слабая ссылка защищает вас от нуля в будущем, а не до

vigdora 16.04.2023 10:01

также я нашел этот пост связанным - stackoverflow.com/questions/50582360/…. особенно последний ответ.

vigdora 16.04.2023 10:24

Используя ответ Роба и прочитав дополнительное чтение, я думаю, мне удалось пролить больше света на это (по крайней мере, для меня, поскольку ответ Роба, конечно, правильный):
во-первых, концепция слабой ссылки - это игра компилятора, то есть компилятор просматривает первый пример и переводит его в: (как описано Робом)

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 содержит другую неявную ссылку на модель представления. компилятор не может сделать вывод, что явная и неявная модели представления связаны, поэтому слабость не применяется к более поздним

Другие вопросы по теме