При обращении к вычисляемому свойству Swift каждый раз выполняет его getter или может использовать ранее полученное значение?
Семантически Swift выполняет getter вычисляемого свойства при каждом обращении. Значение не сохраняется автоматически между обращениями; повторное использование возможно только при явном кэшировании или благодаря внутренней оптимизации, не меняющей наблюдаемое поведение.
В Swift свойства объединяют доступ к данным и вычисляемым значениям в единую модель обращения через точку. Вычисляемые свойства нужны, чтобы получать производное значение без хранения дублирующего состояния.
Такой подход уменьшает риск рассинхронизации: например, площадь прямоугольника можно вычислять из ширины и высоты, не сохраняя отдельное поле площади.
Если разработчик ожидает, что getter выполнится только один раз, он может ошибочно рассчитывать на кэширование результата. Это приводит к лишним вычислениям, повторным побочным эффектам или некорректным выводам при изменении исходных свойств.
Вычисляемое свойство также не занимает отдельное хранилище для своего значения. Если значение нужно сохранить после первого вычисления, это требование нельзя реализовать одним только объявлением getter.
У вычисляемого свойства есть getter, а при необходимости — setter. Getter вычисляет и возвращает значение во время обращения к свойству. Если исходные данные изменились, следующее обращение обычно возвращает результат на основе уже нового состояния.
В примере area не хранится отдельно. Первый доступ вычисляет значение 12, после изменения width следующий доступ вычисляет уже 20.
Компилятор может оптимизировать вычисления, если это безопасно, но программа не должна зависеть от такой оптимизации. Если getter имеет побочные эффекты, они семантически должны происходить при каждом обращении.
Для кэширования используют отдельное хранимое свойство, обычно с флагом готовности, либо lazy stored property, если значение можно вычислить при первом доступе и затем хранить. У такого решения есть компромиссы: кэш нужно инвалидировать при изменении исходных данных, а lazy не подходит для всех сценариев и не является механизмом автоматического отслеживания зависимостей.
В модели документа вычисляется дорогостоящий индекс поиска. Реализация вычисляемого свойства напрямую пересчитывает индекс при каждом обращении. Это просто и всегда использует актуальные данные, но может ухудшить производительность при частом чтении.
Первый вариант — оставить вычисляемое свойство без кэша. Его плюс — отсутствие устаревшего состояния; минус — повторные вычисления. Второй вариант — хранить индекс и обновлять его вручную после каждого изменения документа. Он быстрее при чтении, но сложнее и опаснее: пропущенное обновление даст устаревший результат.
Практичный выбор зависит от характера данных. Для дешёвого вычисления лучше оставить getter, а для дорогого — добавить явный кэш с понятным правилом его сброса. Важно не предполагать, что Swift сам сохранит результат вычисляемого свойства.
lazy?Нет. lazy применяется к хранимым свойствам, потому что ему требуется место для сохранения вычисленного значения. Для вычисляемого свойства нужно явно добавить хранимое свойство-кэш или использовать другой механизм кэширования.
willSet и didSet непосредственно к вычисляемому свойству?Нет. Наблюдатели применяются к хранимым свойствам и отслеживают присваивание хранимого значения. У вычисляемого свойства поведение записи задаётся его setter; проверку, нормализацию или побочные действия нужно реализовать внутри setter самостоятельно.
Для структуры setter по умолчанию считается изменяющим экземпляр, поскольку он потенциально меняет его состояние. Однако для специальных случаев можно объявить setter как nonmutating, если он не изменяет саму структуру, например записывает значение во внешнее ссылочное хранилище. Это меняет правила вызова свойства через константный экземпляр, поэтому использовать такой подход следует только при действительно ссылочной семантике хранения.