> Как изменить значение, захваченное замыканием в Swift (iOS, Swift)

Уровень: senior · Роль: mobile · Категория: Технические вопросы

Компании: Яндекс

Стек: iOS, Swift

> Пример ответа

Короткий ответ

Замыкания в Swift захватывают значения по ссылке или по значению в зависимости от типа. Чтобы изменить захваченное значение, нужно объявить переменную через var и захватить её в списке захвата как inout параметр, либо использовать ссылочный тип (класс). Для изменения значения внутри замыкания, переданного как параметр, используется модификатор inout в сигнатуре замыкания.

Подробное объяснение

В Swift замыкания захватывают переменные из окружающего контекста. По умолчанию захват происходит по значению для value-типов (struct, enum) и по ссылке для reference-типов (class). Однако есть нюанс: если переменная объявлена как var и захвачена замыканием, она захватывается по ссылке, и её можно изменять внутри замыкания.

Основные способы изменения захваченного значения:

  1. Захват переменной var - если переменная объявлена как var в окружающем контексте, замыкание захватывает её по ссылке, и изменения внутри замыкания будут видны снаружи.

  2. Использование inout параметра - если замыкание передаётся как параметр функции, можно объявить параметр как inout, чтобы изменять значение внутри замыкания и возвращать изменения наружу.

  3. Использование ссылочного типа - обернуть значение в класс или использовать Box для хранения value-типа.

  4. Список захвата с weak или unowned - для избежания retain cycles, но это не влияет на возможность изменения.

Важно помнить: если переменная объявлена как let, её нельзя изменить внутри замыкания. Также, если замыкание хранится как свойство и захватывает self, нужно быть осторожным с retain cycles.

На практике

На практике чаще всего встречается два сценария:

  • Изменение внешней переменной внутри замыкания - например, в completion handler или в замыкании, передаваемом в map/filter. Здесь достаточно объявить переменную как var и изменять её внутри замыкания.

  • Изменение значения, переданного в замыкание как параметр - например, в функции сортировки или в кастомном алгоритме. Здесь используется inout параметр.

Также стоит помнить о конкурентности: если замыкание выполняется асинхронно, изменение захваченной переменной может привести к гонке данных. В таких случаях лучше использовать потокобезопасные механизмы, например, NSLock или DispatchQueue.

Пример кода

SWIFT
// Пример 1: изменение внешней переменной
var counter = 0
let increment = {
counter += 1
}
increment()
increment()
print(counter) // 2
// Пример 2: inout параметр
func applyOperation(_ operation: (inout Int) -> Void, to value: inout Int) {
operation(&value)
}
var number = 10
applyOperation({ $0 *= 2 }, to: &number)
print(number) // 20
// Пример 3: захват по ссылке через класс
final class Box<T> {
var value: T
init(_ value: T) {
self.value = value
}
}
let box = Box(5)
let closure = {
box.value += 1
}
closure()
print(box.value) // 6

Как отвечать на собеседовании

Начните с краткого определения замыкания и его поведения при захвате. Затем перечислите основные способы изменения захваченного значения: через var, через inout, через ссылочный тип. Приведите пример для каждого способа. Упомяните про retain cycles и конкурентность как дополнительные нюансы. Если спросят про @escaping - объясните разницу между escaping и non-escaping замыканиями и как это влияет на захват.

Что проверяет интервьюер

Интервьюер проверяет:

  • понимание семантики захвата в Swift (по значению vs по ссылке);
  • знание модификатора inout и его применения;
  • умение работать с reference-типами для обхода ограничений value-типов;
  • осведомлённость о проблемах с retain cycles и конкурентностью;
  • способность объяснить разницу между let и var в контексте замыканий.

Типичные ошибки

  • Попытка изменить let переменную внутри замыкания - компилятор выдаст ошибку.
  • Забывают, что захват value-типа по значению не изменяет оригинал, если переменная объявлена как let.
  • Использование inout с замыканиями, которые хранятся как свойства - это невозможно, inout работает только с параметрами функций.
  • Не учитывают retain cycles при захвате self в замыкании, хранящемся как свойство.
  • Изменение захваченной переменной в асинхронном замыкании без синхронизации - приводит к гонке данных.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью