Для чего используется `[weak self]` в замыканиях Swift и приведи пример?

Ответ

[weak self] используется в списке захвата замыкания для предотвращения циклических сильных ссылок (retain cycles). Он создает слабую (weak) ссылку на self, которая автоматически становится nil, если объект, на который она указывает, уничтожается.

Пример: асинхронная загрузка данных в UIViewController.

class MyViewController: UIViewController {
    let networkService = NetworkService()

    func fetchUserData() {
        // Захватываем weak-ссылку на self, чтобы избежать retain cycle:
        // ViewController -> networkService -> closure -> (strong) ViewController
        networkService.fetchData { [weak self] result in
            // 1. Проверяем, что self еще существует (не был освобожден)
            guard let self = self else {
                print("ViewController deallocated, ignoring callback.")
                return
            }
            // 2. Используем self для обновления UI
            self.updateUI(with: result)
            self.saveToCache(result)
        }
    }

    private func updateUI(with result: DataResult) { /* ... */ }
    private func saveToCache(_ result: DataResult) { /* ... */ }
}

Когда использовать [weak self]:

  1. Асинхронные операции: При работе с DispatchQueue, URLSession, Operation, коллбэками, где self может быть освобожден до выполнения замыкания.
  2. Замыкания, хранимые в свойствах: Когда замыкание, захватывающее self, сохраняется в свойстве того же самого объекта (например, обработчик в UIButton).

Альтернатива [unowned self]: Используется, когда вы уверены, что объект (self) точно переживет замыкание. При нарушении этого условия приведет к крашу (EXC_BAD_ACCESS). Пример: анимация, которая точно завершится до деаллокации контроллера.

UIView.animate(withDuration: 0.3) { [unowned self] in
    self.view.alpha = 0.0 // Рискованно, если контроллер может быть освобожден во время анимации.
}

Best practice: В большинстве асинхронных сценариев безопаснее использовать [weak self] с последующей проверкой через guard let или optional chaining (self?.updateUI()).

Ответ 18+ 🔞

Да блядь, слушай, вот эта вся история с [weak self] — это ж классика, как Герасим с Муму, только в мире свифта. Представь себе: сидит твой UIViewController, здоровый такой мужик, как Герасим, и думает: «Ща я данные с сервака запрошу, всё будет пиздато».

И он вызывает какую-нибудь сетевую хуйню:

networkService.fetchData { result in
    self.updateUI(with: result) // ОЙ, БЛЯДЬ! СТОП!
}

Вот тут-то и начинается трагедия, ёпта! Смотри, что происходит:

Твой ViewController (пусть будет Вася) держит сильную ссылку на networkService. networkService начинает запрос и держит в своих потрохах это замыкание. А замыкание-то, хитрая жопа, внутри себя держит сильную ссылку на self, то есть на самого Васю.

Получается пиздецкий круг: Вася -> сервис -> замыкание -> Вася. Они все друг за друга держатся, как три пьяных мужика у параши. И когда Вася захочет уйти нахуй (деаллоцироваться), он не сможет! Его не отпустит замыкание, которое не отпустит сервис, который не отпустит самого Васю. И будет висеть он в памяти, как призрак, до конца времён, пока приложение не накроется медным тазом. Это и есть retain cycle, блядь, или «циклическая сильная ссылка» для распиздяев.

И вот тут на сцену выходит наш спаситель — [weak self]. Это как дать Герасиму не верёвку, а слабую ниточку.

networkService.fetchData { [weak self] result in
    // Ссылка на self теперь weak — она не держит Васю за шкирку!
    // Если Васю прибьют (деаллоцируют), ссылка просто станет nil.
}

Но и тут, блядь, парадокс! Ты же не можешь просто так взять и использовать self — он же опциональный теперь, этот твой Вася! Может оказаться, что пока запрос летел, пользователь нажал назад, контроллер сдох, а замыкание прилетело в пустоту.

Поэтому делаем, как умные люди:

networkService.fetchData { [weak self] result in
    guard let self = self else {
        print("ViewController deallocated, ignoring callback.") // Вася сдох, иди нахуй
        return
    }
    // А вот тут self уже сильный и уверенный в себе!
    self.updateUI(with: result)
    self.saveToCache(result)
}

Когда это, блядь, нужно применять?

  1. Асинхронщина всякая: DispatchQueue, URLSession, коллбэки — везде, где ответ может прилететь, когда тебя уже нет.
  2. Когда замыкание живёт дольше тебя: Сохранённые в свойствах обработчики, таймеры — вот эта вся хуйня.

А есть же ещё [unowned self], блядь! Это как поставить на то, что Герасим точно не утопит Муму. Ты говоришь: «Да похуй, self точно будет жив, когда замыкание вызовется!». Но если ошибёшься — EXC_BAD_ACCESS, в рот меня чих-пых, приложение рухнет, как ядро чистый изумруд.

UIView.animate(withDuration: 0.3) { [unowned self] in
    self.view.alpha = 0.0 // Если анимация вызовется после смерти self — пиздец тебе, а не анимация.
}

Так что, блядь, правило простое: В 95% случаев, особенно с сетью и асинхронными операциями, используй [weak self] и потом guard let. Не будь максималистом, как тот Герасим. Дай всем пожить, в рот меня чих-пых! Захотел топить — проверь сначала, жива ли твоя слабая ссылка.