Pull to refresh

Comments 18

Спасибо за статью! Интересный подход.

А Вы не замеряли, есть ли заметные накладные расходы по производительности при использовании этого подхода?

Насколько часто Вы пользуетесь этими аннотациями? Только для ViewModel или для каких-то более мелких объектов тоже?

Вы оставляете эти проверки в release-версии?

Примерно аналогичный описанному выше функционал предоставляет инструмент Leaks в Instruments. На нём подробно останавливаться не будем.

Но ведь не совсем? Instruments позволяет именно обнаружить утечки, они "подсвечиваются" по ходу работы приложения. Или применительно к SwiftUI этот инструмент работает не очень хорошо? Я в SwiftUI пока не пробовал им пользоваться.

А Вы не замеряли, есть ли заметные накладные расходы по производительности при использовании этого подхода?

Какие-то расходы есть, конечно, но они не сравнимы с последствиями утечки

Насколько часто Вы пользуетесь этими аннотациями? Только для ViewModel или для каких-то более мелких объектов тоже?

В основном – для моделей. Обычно, если "утекает" что-то мелкое внутри модели – сама модель тоже не освобождается. Однако, ограничений нет.

Вы оставляете эти проверки в release-версии?

Нет, в реальном коде проверки закрыты аннотациями #if DEBUG || ADHOC

Но ведь не совсем? Instruments позволяет именно обнаружить утечки, они "подсвечиваются" по ходу работы приложения. Или применительно к SwiftUI этот инструмент работает не очень хорошо? Я в SwiftUI пока не пробовал им пользоваться.

В самом Xcode примерно также, только нужно останавливать выполнение. Однако, не это главное. В описанном варианте утечку может обнаружить QA-инженер, тестирующий на реальном устройстве, а не только разработчик в Xcode/Instruments, когда специально этим озаботится. Разработчик может спокойно писать код, зная, что утечки будут найдены в процессе тестирования.

Нет, в реальном коде проверки закрыты аннотациями #if DEBUG || ADHOC

Тогда все вопросы к накладным расходам снимаются)

Вы могли просто добавить deinit с проверкой в класс LeakingViewModel и таким образом контролировать удаление объекта из памяти.

Если объект LeakingViewModel "утечёт" – его deinit не будет вызван, соответственно, проверка не состоится.

Всё правильно, таким образом вы и узнаете, что есть утечка, потому что deinit не был вызван.

В тот же момент, когда вы ожидаете вызов метода deinit в вашем примере у классов LeakWatcher и property wrapper.

Мы с FrD1 разные люди, я просто тоже не совсем понял, что Вы имеете в виду) Вы не могли бы пояснить? Что бы Вы написали в deinit ViewModel-и / ViewController-а? И сделали ли бы Вы какие-нибудь ещё изменения?

Да ничего особенного. Можно просто добавить, например,

    deinit {

        debugPrint("👍 deinit \(self)")

    }

И, соответственно, проконтролировать, что происходит вызов deinit там, где используется этот класс. Если в логах ничего нет, значит есть утечка.

А, я подумал, что Вы подход автора предлагаете доработать) Это-то понятно, я сам так и делаю обычно. Но это неравноценно.

Во-первых, это может сделать только разработчик, потому что только разработчик владеет полной информацией о том, какой метод когда должен быть вызван, какие внутри объекты и т.д. Да и то, когда проект большой, один разработчик знает одну часть, другой другую. Части друг друга они знают, но не досконально. Соответственно, во время тестирования (отделом QA) это делаться не будет. Во-вторых, это "ручной" подход. А это значит, что он муторный, времязатратный и провоцирует ошибки. В случае подхода из статьи же мы можем быть уверены, что если ViewModel (или что-либо, что мы отслеживаем) утечёт – мы об этом узнаем. Причём хоть сейчас, хоть в QA. И мы можем больше об этом не думать.

Так что то, что Вы предлагаете – вполне рабочий подход, имеющий своё место, но подход в статье обладает несомненными плюсами.

Если deinit у классов LeakWatcher и property wrapper не будет вызван из-за утечки, вы точно также об этом никогда не узнаете)

Да, но мы их вручную не создаём. Они не утекут.

Я бы не обольщался) При смешении value и reference type структура View может оказаться в куче и утекшее свойство ссылочного типа будет держать в памяти всю структуру и, соответственно, не вызовет deinit у классов LeakWatcher и property wrapper.

Про View в куче я, боюсь, не понял. Вы не могли бы пояснить, пожалуйста? LeakWatcher лежит в State / StateObject, жизненный цикл которого управляется SwiftUI, мы туда не лезем. Ссылок на само View мы вообще нигде не храним. С LeakWatcher напрямую не работаем.

На практике, ложноположительные срабатывания могут быть, если модель очень сложная и разветвлённая, со множеством вложенных объектов и зависимостей. Мы заметили, что для того, чтобы полностью её освободить может не хватать дефолтной задержки в одну секунду. Собственно, этим и объясняется наличие опционального параметра задержки у враппера.

В остальных случаях, когда модель по каким-то причинам создаётся не один раз и предыдущий экземпляр не освобождается, мы уже не можем считать, что срабатывание было ложным.

Если посмотреть предложенные ссылки, там всё сводится как раз к такой ситуации. Относительно LazyVStack и им подобным, это – известная проблема. Похожее поведение будет и в UIKit, если модель UITableViewCell создавать внутри самого объекта UITableViewCell.

Для примеров в статье был выбран StateObject из соображений компактности примеров. В архитектуре реального проекта, использование StateObject не представляется оправданным, т.к. это не соответствует понятиям чистой архитектуры и SOLID. Проще говоря, мы не используем StateObject вообще, все модели у нас – ObservableObject, которые создаются однократно, вне скоупа SwiftUI. Соответственно, при переиспользовании View, они получают одну и ту же ссылку на один и тот же объект.

Sign up to leave a comment.