Программирование SwiftSwift CoreМладший iOS-разработчик

Что означает lazy для хранимого свойства Swift при первом обращении?

Что означает lazy для хранимого свойства Swift при первом обращении?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Свойство с модификатором lazy не вычисляется при создании экземпляра. Его значение создаётся при первом обращении, сохраняется в экземпляре и затем возвращается без повторной инициализации.

Исторический контекст

Отложенная инициализация нужна, чтобы не тратить ресурсы на данные, которые могут никогда не понадобиться. Свойство-level механизм позволяет описать такую стратегию рядом с самим свойством, не перенося управление кэшированием в вызывающий код.

Постановка проблемы

При обычной инициализации дорогое свойство создаётся для каждого экземпляра сразу, даже если приложение не использует его. При lazy старт становится дешевле, но первый доступ получает дополнительную стоимость и может неожиданно запустить тяжёлую работу.

Важно учитывать, что lazy не является средством синхронизации. При одновременном первом доступе из нескольких потоков нельзя полагаться на безопасную однократную инициализацию.

Подробное решение

lazy применяется к хранимому свойству, которое должно быть объявлено как var. Swift хранит признак того, что значение ещё не создано; при первом чтении выполняется выражение инициализации, после чего результат сохраняется в конкретном экземпляре.

final class Report { lazy var text: String = { print("build") return "готово" }() } let report = Report() print(report.text) print(report.text)

В этом примере build печатается только при первом обращении к text, а не при создании Report. Разные экземпляры имеют разные сохранённые значения свойства.

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

Если инициализация зависит от состояния экземпляра, lazy-свойство удобно тем, что создаётся уже после завершения инициализатора. Однако значение может быть построено несколько раз при небезопасном конкурентном первом доступе, поэтому для потокобезопасной ленивой инициализации нужен отдельный механизм синхронизации.

Ситуация из практики

Экран содержит объект для разбора большого локального файла, который нужен только после перехода пользователя на вкладку «Детали». Вариант с обычным свойством проще и предсказуемее, но замедляет создание экрана и расходует память заранее.

Явный фабричный метод или внешний кэш дают больше контроля: можно обрабатывать ошибки, отмену и синхронизацию. Их минус — дополнительный код и риск забыть корректно закэшировать результат.

Для однопоточного сценария без сложной обработки ошибок разумно выбрать lazy: объект создаётся только при открытии вкладки и затем переиспользуется этим экземпляром экрана. Если создание может выполняться конкурентно или должно возвращать ошибку, лучше применить явный потокобезопасный кэш либо асинхронную загрузку.

Что кандидаты часто упускают

  1. Создаётся ли lazy-свойство заново после каждого чтения?

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

  1. Можно ли обращаться к lazy-свойству из нескольких потоков без защиты?

Нельзя считать такой доступ безопасным. Если несколько потоков одновременно выполняют первый доступ, инициализирующее выражение может быть запущено конкурентно, а доступ к сохранённому состоянию может привести к гонке. lazy откладывает вычисление, но не заменяет блокировку, actor или другой механизм синхронизации.

  1. Чем lazy-свойство структуры отличается от lazy-свойства класса?

У класса состояние принадлежит ссылочному экземпляру, поэтому инициализация изменяет состояние объекта, даже если ссылка объявлена через let. У структуры доступ к lazy-свойству может изменять само значение структуры, поскольку в него записывается вычисленный результат; поэтому для такой операции требуется изменяемый экземпляр. Кроме того, копии структур имеют независимое состояние после семантически необходимого разделения значения.