← Назад к вопросам

Как применяются слабые ссылки в замыканиях?

2.0 Middle🔥 271 комментариев
#Управление памятью#Язык Swift

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Применение слабых ссылок в замыканиях в iOS-разработке

Слабые ссылки (weak references) в замыканиях используются для предотвращения циклов сильных ссылок (retain cycles) — ситуации, когда два объекта удерживают друг друга, что приводит к утечкам памяти, поскольку счетчики ссылок никогда не достигают нуля.

Основная проблема: Цикл сильных ссылок с замыканиями

Замыкания в Swift захватывают внешние переменные и константы, создавая сильные ссылки на них. Когда объект хранит замыкание как свойство, а замыкание захватывает self, возникает взаимное удержание:

class MyViewController: UIViewController {
    var dataHandler: (() -> Void)?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // ПРОБЛЕМА: замыкание захватывает self сильно
        dataHandler = {
            self.updateUI() // Сильная ссылка на self
        }
    }
    
    func updateUI() {
        // Обновление интерфейса
    }
    
    deinit {
        print("MyViewController деинициализирован") // Не будет вызван!
    }
}

В этом примере MyViewController сильно ссылается на dataHandler, а замыкание сильно ссылается на self (MyViewController). После уничтожения контроллера извне счетчик ссылок остается равным 1 из-за замыкания.

Решения с использованием слабых ссылок

1. Использование [weak self]

class MyViewController: UIViewController {
    var dataHandler: (() -> Void)?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // РЕШЕНИЕ: weak capture
        dataHandler = { [weak self] in
            self?.updateUI() // self теперь Optional
        }
    }
    
    func updateUI() {
        print("UI updated")
    }
    
    deinit {
        print("MyViewController деинициализирован") // Будет вызван!
    }
}

Преимущества:

  • Разрывает цикл сильных ссылок
  • self становится опциональным (ViewController?)
  • Автоматически становится nil при деаллокации объекта

Особенности:

  • Необходимость проверки self? перед использованием
  • Замыкание может выполниться с nil, если объект уже уничтожен

2. Использование [unowned self]

dataHandler = { [unowned self] in
    self.updateUI() // self не опциональный
}

Отличия от weak:

  • unowned предполагает, что объект существует на протяжении всего времени жизни замыкания
  • Не делает ссылку опциональной
  • При обращении к уничтоженному объекту вызывает краш (небезопасно)
  • Используется, когда замыкание точно не переживет объект

3. Комбинированный подход с guard

dataHandler = { [weak self] in
    guard let strongSelf = self else { return }
    strongSelf.updateUI()
    strongSelf.processData()
}

Этот подход безопасен и удобен, когда в замыкании много обращений к self.

Практические сценарии использования

Асинхронные операции

class DataLoader {
    func loadData(completion: @escaping (Result<Data, Error>) -> Void) {
        URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
            // weak self, так как мы не хотим удерживать DataLoader
            self?.handleResponse(data: data, error: error)
            completion(.success(data))
        }.resume()
    }
}

Обработчики в UI-компонентах

class CustomButton: UIButton {
    var tapAction: (() -> Void)?
    
    func setupTapHandler() {
        addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
    }
    
    @objc private func buttonTapped() {
        // Замыкание может захватывать внешний контроллер weakly
        tapAction?()
    }
}

// Использование
myButton.tapAction = { [weak self] in
    self?.handleButtonTap()
}

GCD и операции

DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { [weak self] in
    self?.showNotification() // Не выполнится, если self уже уничтожен
}

Особые случаи и рекомендации

  1. Когда НЕ использовать weak:

    • Замыкания, которые точно выполнятся до деаллокации объекта
    • @escaping замыкания, которые хранятся недолго
    • Ситуации, где гарантированно нет цикла ссылок
  2. Проблема "промежуточного" удержания:

    networkService.fetchData { [weak self] result in
        // self может быть уничтожен между асинхронными операциями
        self?.process(result)
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            self?.updateUI() // self уже может быть nil
        }
    }
    
  3. Использование с self в замыканиях, которые не escaping:

    func localOperation() {
        // Не @escaping замыкание - weak не нужен
        let multiplier = 5
        let closure = {
            self.value * multiplier // Нет цикла ссылок
        }
        closure()
    }
    

Итог

Слабые ссылки в замыканиях — критически важный механизм управления памятью в iOS-разработке. Правильное применение [weak self] предотвращает утечки памяти, но требует:

  • Понимания жизненных циклов объектов
  • Аккуратной обработки опциональных self
  • Выбора между weak и unowned в зависимости от гарантий времени жизни
  • Тестирования сценариев деаллокации объектов

Игнорирование слабых ссылок в @escaping замыканиях, захватывающих self, — одна из самых распространенных причин утечек памяти в iOS-приложениях.