Какие проблемы с рекурсией в геттерах и как от нее избавиться
Что проверяют
Понимаете ли вы разницу между хранимым и вычисляемым свойством и откуда в геттере берётся бесконечный вызов самого себя. Следом часто спрашивают про потокобезопасные геттеры: где там прячутся повторный вход и взаимоблокировка.
Как отвечать
Назовите основные сценарии:
- обращение к себе:
var name: String { name }вызывает тот же геттер снова и снова, стек переполняется, приложение падает сEXC_BAD_ACCESS. Компилятор предупреждает, что свойство читается внутри собственного геттера, но предупреждение легко пропустить. То же с сеттером:set { name = newValue }зовёт сам себя; - Objective-C:
self.nameвнутри метода-nameснова отправляет сообщение геттеру. Внутри аксессоров обращаются к ivar_nameнапрямую; - взаимная рекурсия:
aчитаетb, аbчитаетa, иногда через цепочку из трёх методов. Здесь компилятор уже молчит; - повторный вход под замком: геттер берёт
NSLockили делаетqueue.sync, а внутри читает другое свойство, защищённое тем же замком или той же очередью. Результат не переполнение стека, а deadlock.
Как лечить:
- отдельное хранилище: приватное
_nameили хранимое свойство сdidSet. Присваивание самому свойству внутри егоdidSetнаблюдатели повторно не вызывает, поэтому там можно, например, обрезать значение; - кэш вместо пересчёта:
lazy varдля дорогого значения, но его инициализация не потокобезопасна, и два потока могут посчитать значение дважды; - замок только снаружи: публичный геттер берёт замок и вызывает приватные методы без замка.
NSRecursiveLockтоже спасает, но чаще маскирует плохую структуру.
Где спотыкаются
Не знают, как ведёт себя didSet: боятся рекурсии там, где её нет, и заводят лишнее поле, зато в собственном set вычисляемого свойства спокойно пишут name = newValue.
Прячут тяжёлую работу и побочные эффекты в вычисляемое свойство. Читающий код ждёт дешёвого чтения, а получает сетевой запрос или запись в базу.
Думают, что рекурсия бывает только явной. Цепочку через KVO или @Published, у которого подписчик в sink снова пишет в то же свойство, находят только по трассировке стека.