В приложении несколько потоков одновременно впервые обращаются к статическому хранимому свойству Swift. Как Swift гарантирует его инициализацию?
Статическое хранимое свойство инициализируется лениво — при первом обращении к нему. Swift гарантирует, что его инициализирующее выражение выполнится только один раз, даже если первый доступ происходит одновременно из нескольких потоков.
После завершения инициализации все последующие обращения используют одно общее значение, хранящееся на уровне типа, а не отдельного экземпляра.
Типовые свойства нужны для состояния, общего для всех экземпляров типа: например, для конфигурации, кэша или счётчика. Ленивая инициализация позволяет не выполнять потенциально дорогую работу при запуске приложения, если свойство вообще не будет использовано.
Гарантия однократной инициализации решает отдельную проблему многопоточности: разработчику не требуется вручную защищать самый первый доступ блокировкой только ради создания общего значения.
Если несколько потоков впервые обращаются к общему свойству без синхронизации, теоретически возможны повторное создание ресурса, потеря одного из созданных экземпляров или гонка при записи общего состояния.
Для статического хранимого свойства Swift устраняет именно гонку инициализации. Это не означает, что любые последующие изменения содержимого свойства автоматически становятся безопасными: если само значение изменяемо, доступ к нему может потребовать отдельной синхронизации.
Типовое свойство объявляется с модификатором static и принадлежит типу. Оно существует в единственном экземпляре независимо от количества созданных экземпляров самого типа.
В этом примере сообщение об инициализации печатается только при первом обращении к ImageCache.shared. Второе обращение не запускает инициализирующее выражение повторно.
Ленивая инициализация относится к самому хранимому типовому свойству, а не к экземплярам класса. Обращение к свойству через один экземпляр и через другой, если такой доступ разрешён контекстом, всё равно должно концептуально работать с одним общим хранилищем типа.
Важно различать однократную инициализацию и потокобезопасность состояния. Swift защищает момент создания значения, но не превращает, например, словарь в потокобезопасную коллекцию: одновременные записи в него могут требовать actor, serial queue, lock или другого механизма синхронизации.
Команде нужно один раз создать общий кэш изображений. Вариант с обычным свойством экземпляра создаёт отдельный кэш для каждого объекта и расходует память. Вариант с глобальной переменной обеспечивает единое хранилище, но хуже связывает состояние с предметным типом и расширяет область доступа.
Выбранное статическое свойство типа даёт единую точку доступа и ленивое создание. Однако сам словарь дополнительно защищают от конкурентных изменений, например помещая состояние в actor; это сохраняет преимущество общей инициализации и устраняет гонки при дальнейшей работе с кэшем.
Вопрос: Инициализируется ли статическое хранимое свойство при создании первого экземпляра типа?
Ответ: Нет, не обязательно. Оно инициализируется при первом обращении именно к этому свойству, даже если экземпляры типа уже существуют. Создание экземпляра и доступ к типовому свойству — независимые события.
Вопрос: Делает ли гарантия однократной инициализации последующие изменения статического свойства безопасными?
Ответ: Нет. Гарантия касается выполнения инициализирующего выражения и публикации первоначального значения. Если после этого несколько потоков одновременно изменяют общее ссылочное или изменяемое значение, для таких операций нужна отдельная синхронизация.
Вопрос: Что изменится, если статическое свойство объявить как let, а не как var?
Ответ: Момент и гарантия инициализации останутся теми же: значение будет создано лениво и только один раз. let запрещает переназначить само свойство после инициализации, тогда как var допускает переназначение; это ограничение мутабельности не заменяет потоковую синхронизацию для внутреннего состояния ссылочного объекта.