Как изменить значение, захваченное замыканием в Swift
Что проверяют
Знаете ли вы, что именно захватывает замыкание в Swift: саму переменную или её копию. Следом обычно спрашивают про список захвата, inout в escaping-замыкании и циклы сильных ссылок.
Как отвечать
Главное правило: замыкание захватывает переменную по ссылке, даже если это Int или структура. Оно держит не значение на момент создания, а саму ячейку памяти, поэтому изменение видно с обеих сторон:
var counter = 0
let increment = { counter += 1 }
increment()
print(counter) // 1Дальше разберите варианты:
- обычная
var: объявить какvarи менять внутри замыкания. Если замыкание escaping, переменная переезжает в кучу и живёт, пока живёт замыкание; - список захвата
[counter]: копирует значение в момент создания. Внутри это константа, изменить её нельзя, а внешние изменения замыкание не увидит; - свойство класса: замыкание держит ссылку на объект, и
self.count += 1меняет общее состояние. Если объект сам хранит это замыкание, нужен[weak self]или[unowned self], иначе получится цикл ссылок; inout-параметр: захватить его можно только в non-escaping замыкании. В escaping компилятор выдаст ошибку: после возврата из функцииinoutуже не существует. То же сselfвнутриmutating-метода структуры;- конкурентный код: менять захваченную
varвнутри@Sendable-замыкания компилятор не даст. Изменяемое состояние выносят вactorили в класс под блокировкой.
Где спотыкаются
Говорят, что структуры захватываются по значению, а классы по ссылке. Это путаница с семантикой присваивания: переменную замыкание захватывает по ссылке всегда, копию даёт только список захвата.
Предлагают inout как способ изменить захваченное значение. Это механизм передачи параметра, а не захвата, и с escaping-замыканием он не работает.
Ставят [weak self] везде по привычке и не могут объяснить, где цикл на самом деле. В UIView.animate его нет, и интервьюер это заметит.