> В чем разница между захватом переменной в closure и копированием (iOS, Swift)

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

Компании: Wildberries

Стек: iOS, Swift

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

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

Захват переменной в closure - это ссылка на существующую область памяти, а копирование - создание независимой копии значения. В Swift closure по умолчанию захватывает ссылки на reference types и значения для value types, но с нюансами: value types копируются при захвате, если не используется capture list с явным указанием. Копирование гарантирует неизменяемость исходных данных, а захват позволяет closure влиять на внешнее состояние.

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

В Swift closure - это reference type, который хранит захваченные переменные в собственной области памяти. При захвате переменной типа class (reference type) closure хранит ссылку на тот же объект - изменения внутри closure видны снаружи. Для struct и enum (value types) захват происходит по значению: closure получает копию на момент создания, если не указано иное.

Ключевой момент - capture list [weak self] или [unowned self] для reference types, который позволяет управлять силой ссылки и избегать retain cycles. Для value types capture list может явно указать копирование: [value = someValue] - это создаст копию на момент создания closure.

Разница проявляется в:

  • Память: захват ссылки не дублирует данные, копирование - дублирует.
  • Время жизни: захваченная ссылка может указывать на освобождённый объект (если weak), копия всегда валидна.
  • Побочные эффекты: мутация захваченной reference type внутри closure изменяет оригинал, мутация копии - нет.

Для value types в Swift есть оптимизация copy-on-write (у Array, Dictionary, String): копирование происходит лениво, только при мутации. Но это не отменяет семантику - closure работает с копией, а не с оригиналом.

На практике

В iOS-разработке чаще всего встречается захват self в замыканиях - например, в UIView.animate, DispatchQueue.async или в completion handlers. Здесь критично понимать разницу между сильным и слабым захватом.

Типичный сценарий: замыкание захватывает self (ViewController), который содержит это замыкание - возникает retain cycle. Решение - [weak self] в capture list. Для value types (например, struct модель) захват по значению безопасен, но если нужно изменить внешнюю переменную - потребуется явное копирование или использование inout параметра.

На практике также важно помнить: если closure захватывает var value type и мутирует её, Swift требует явного указания var в capture list: [var value = someValue]. Это редкий случай, но он подчёркивает разницу между захватом и копированием.

Пример кода

SWIFT
final class Counter {
var count = 0
}
var counter = Counter()
var number = 10
let closure = {
counter.count += 1 // захват ссылки - изменение видно снаружи
number += 1 // захват копии - изменение не видно снаружи
}
closure()
print(counter.count) // 1
print(number) // 10 - оригинал не изменился
// Явное копирование value type через capture list
var originalValue = 5
let copyClosure = { [value = originalValue] in
print(value) // всегда 5, даже если originalValue изменится
}
originalValue = 100
copyClosure() // 5
// Weak захват для избежания retain cycle
class ViewModel {
var onUpdate: (() -> Void)?
func setup() {
onUpdate = { [weak self] in
self?.doSomething()
}
}
func doSomething() {}
}

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

Начни с чёткого определения: захват - это ссылка, копирование - независимое значение. Затем переходи к Swift-специфике: value types копируются, reference types - ссылаются. Обязательно упомяни capture list и retain cycles - это ключевой момент для senior-позиции.

Покажи понимание copy-on-write и его влияния на производительность. Расскажи о практическом применении: когда использовать [weak self], а когда - сильный захват. Приведи пример из реальной iOS-разработки: замыкание в URLSession completion handler.

Если спросят про @escaping - свяжи с временем жизни захваченных переменных. Подчеркни, что для value types захват по значению - это безопасно, но может привести к неожиданному поведению, если разработчик ожидает мутацию оригинала.

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

Интервьюер оценивает:

  • Понимание семантики value vs reference types в Swift.
  • Знание capture list и его синтаксиса.
  • Умение объяснить retain cycle и способы его избежания.
  • Понимание copy-on-write и его влияния на память.
  • Способность применить теорию к реальным сценариям iOS-разработки.

Для senior-уровня важно показать не просто знание фактов, а понимание trade-off: когда захват ссылки оправдан (например, для shared state), а когда копирование безопаснее (для изоляции данных). Также проверяется умение объяснить разницу между weak и unowned и когда какой использовать.

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

  • Утверждение, что closure всегда захватывает ссылку - для value types это не так.
  • Игнорирование retain cycle при сильном захвате self в замыкании.
  • Путаница между weak и unowned: unowned предполагает, что объект живёт дольше closure, иначе - crash.
  • Незнание, что Array, Dictionary, String - value types с copy-on-write, а не reference types.
  • Ожидание, что мутация захваченной копии value type повлияет на оригинал - это не так без явного inout или capture list с var.
  • Забывание, что capture list с [value = someValue] создаёт копию на момент создания closure, а не на момент вызова.

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

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