Как замыкания (closures) в Swift захватывают переменные: по значению или по ссылке?

Ответ

По умолчанию — по сильной ссылке (strong reference). Механизм зависит от типа переменной и использования списка захвата (capture list).

  1. Reference-типы (классы): Захватываются по сильной ссылке. Это может создать цикл сильных ссылок (retain cycle).

    class MyClass {
        var value = 0
        lazy var closure: () -> Void = {
            print(self.value) // Захват `self` по сильной ссылке!
        }
    }
  2. Value-типы (структуры, перечисления): По умолчанию также захватываются по ссылке на исходный экземпляр. Чтобы захватить копию значения на момент создания замыкания, используйте список захвата:

    var number = 42
    let closure = { [number] in // Захват КОПИИ значения `number`
        print(number)
    }
    number = 100
    closure() // Выведет: 42
  3. Управление ссылками для классов: Во избежание циклов используйте в списке захвата weak или unowned.

    let closure = { [weak self] in
        guard let self = self else { return }
        self.doSomething() // Безопасная работа с опциональной ссылкой
    }

Ответ 18+ 🔞

А, слушай, вот этот момент про замыкания — он же, блядь, как мина замедленного действия, если не въехать! Ёпта, народ на этом так обжигается, что потом волосы дыбом встают.

Ну смотри, по дефолту-то всё просто, как три копейки: захватывает по сильной ссылке, по-братски, намертво. Но вся хуйня в том, что механизм-то разный для классов и для структур!

С классами — вот тут пиздец и начинается. Берёт он self в замыкание и держит, как удав кролика. А если этот self сам держит это замыкание как свойство — привет, ёбаный retain cycle, память течёт, приложение падает, и ты потом три ночи дебажишь, че за хуйня.

class MyClass {
    var value = 0
    lazy var closure: () -> Void = {
        print(self.value) // Смотри-ка, блядь! Захватил `self` намертво! Пиздец, Колян, цикл!
    }
}

Вот это self.value — оно же из замыкания на self ссылается, а self через lazy var на замыкание. Круг замкнулся, ебать! Мертвая хватка.

А вот со структурами и прочей value-хренью — прикол другой. Они вроде как по значению, но замыкание-то по умолчанию тоже ссылку ловит на тот экземпляр, что снаружи. Хочешь снимок, заморозку на момент создания? Тогда, сука, явно в квадратных скобках указывай!

var number = 42
let closure = { [number] in // Вжух! Захватил КОПИЮ, блядь, как есть, 42!
    print(number)
}
number = 100 // Меняй что хошь, похуй.
closure() // Выведет: 42. Потому что мы заморозили старую версию, как мамонта в лёд.

И главный лайфхак для классов, чтобы не было циклов — это, блядь, список захвата (capture list), святое дело! Там weak или unowned юзай.

let closure = { [weak self] in // Ага, слабая ссылка, не держим за яйца!
    guard let self = self else { return } // Проверили, жив ли ещё объект, или уже в рай улетел.
    self.doSomething() // Теперь безопасно работаем, циклов нет, мать вашу!
}

weak — это типа «если объект ещё есть, ок, если его уже нет — ну и похуй, пропускаем». unowned — это когда ты на 146% уверен, что объект переживёт замыкание, иначе креш, пизда.

Вот и вся магия, ёпта. Запомни: по умолчанию — сильная ссылка, для value-типов лови копию через [переменная], а для классов слабь ссылку, а то накосячишь так, что мало не покажется.