Как избежать проблем с нагрузкой из-за Singleton
Что проверяют
Понимаете ли вы, что проблемы с производительностью у синглтона идут не от паттерна, а от общего изменяемого состояния, к которому ходят из многих потоков. Следом обычно просят показать потокобезопасный доступ к данным внутри shared и объяснить, как такой объект подменить в тестах.
Как отвечать
Начните с того, что создание синглтона в Swift уже безопасно: static let shared = Manager() инициализируется лениво при первом обращении и ровно один раз, это гарантирует рантайм. Отдельный dispatch_once в Swift недоступен и не нужен. Проблемы начинаются дальше:
- узкое место на синхронизации: если все методы закрыты одним
NSLockили последовательной очередью, запросы из разных потоков выстраиваются в очередь. Помогает разделить чтение и запись: concurrent-очередь сsyncна чтение иasync(flags: .barrier)на запись, либо сделать сервисactorв Swift Concurrency; - работа на главном потоке: синглтон, который синхронно читает файл, парсит JSON или гоняет большие объёмы через
UserDefaults, тормозит интерфейс. Тяжёлое уносится в фон, наружу отдаётсяasync-метод; - тяжёлая инициализация: первый вызов
sharedна старте приложения тянет за собой базу, сеть и кеши. Инициализацию делайте дешёвой, а ресурсы поднимайте по требованию; - бесконечно растущее состояние: кеш в синглтоне живёт всё время работы приложения. Используйте
NSCache, который сам вытесняет записи при нехватке памяти, или ограничивайте размер; - связность: вместо обращений к
Manager.sharedиз глубины кода передавайте зависимость через протокол в инициализатор. Синглтон остаётся одной точкой сборки, но его можно заменить заглушкой.
Где спотыкаются
Путают потокобезопасность создания и потокобезопасность данных. static let гарантирует один экземпляр, но изменяемый словарь внутри него без синхронизации всё равно ловит гонку данных.
Ставят синхронизацию на всё подряд, включая чтение. Под нагрузкой сервис превращается в последовательную очередь.
Предлагают «заменить синглтон на Factory или Builder». Порождающие паттерны не решают проблему глобального состояния, её решает внедрение зависимостей и явное владение объектом.