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

Как использовать [weak self] и [unowned self] в замыканиях?

2.0 Middle🔥 112 комментариев
#CI/CD и инструменты разработки#Soft Skills и карьера

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

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

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

Управление захватом self в замыканиях Swift

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

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

Цикл возникает, когда объект А сильно ссылается на объект Б, а объект Б сильно ссылается на объект А. В контексте замыканий это часто происходит, когда замыкание, принадлежащее объекту, захватывает этот же объект через self:

class DataManager {
    var data: [String] = []
    lazy var dataProcessor: () -> Void = {
        // Проблема: сильный захват self создает retain cycle!
        self.processData()
    }
    
    func processData() {
        print("Обработка \(data.count) элементов")
    }
    
    deinit {
        print("DataManager освобожден")
    }
}

// При использовании:
var manager: DataManager? = DataManager()
manager?.dataProcessor() // Замыкание создано и захватило self
manager = nil // DataManager НЕ будет освобожден из-за цикла!

Решение 1: [weak self] — безопасный опциональный захват

[weak self] создает слабую ссылку (weak reference) на self, которая автоматически становится nil, когда объект освобождается:

class NetworkService {
    func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
        URLSession.shared.dataTask(with: URL(string: "https://api.example.com")!) { [weak self] data, response, error in
            // self стал опциональным (self?)
            guard let self = self else {
                print("NetworkService был освобожден, отмена обработки")
                return
            }
            
            // После проверки создается временная сильная ссылка
            self.handleResponse(data: data, error: error)
            self.updateUI()
        }.resume()
    }
    
    private func handleResponse(data: Data?, error: Error?) {
        // Обработка ответа
    }
    
    private func updateUI() {
        // Обновление интерфейса
    }
}

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

  • Автоматическая установка в nil при освобождении объекта
  • Предотвращение крашей при обращении к освобожденной памяти
  • Безопасность — компилятор не позволит обратиться напрямую

Недостатки:

  • Необходимость постоянно использовать guard let self = self
  • Дополнительный boilerplate-код

Решение 2: [unowned self] — "безопасный" неопциональный захват

[unowned self] создает бесхозную ссылку (unowned reference), которая предполагает, что объект будет существовать на момент выполнения замыкания:

class AnimationController {
    func startAnimation(completion: @escaping () -> Void) {
        UIView.animate(withDuration: 0.3) { [unowned self] in
            // Прямой доступ к self без опционала
            self.view.alpha = 0.5
            self.updateLayout()
            completion()
        }
    }
    
    private func updateLayout() {
        // Обновление лэйаута
    }
}

Когда использовать unowned self:

  1. Когда жизненный цикл замыкания короче жизненного цикла объекта
  2. В анимациях UIView, где анимация точно завершится до освобождения контроллера
  3. Когда объект и замыкание освобождаются одновременно

Опасность unowned self:

  • Если объект освобожден раньше, чем выполнилось замыкание — приложение крашнется с EXC_BAD_ACCESS
  • Отладка таких крашей сложнее, чем с weak self

Практические рекомендации

Используйте [weak self] когда:

  • Работаете с сетевыми запросами, которые могут выполняться долго
  • Замыкание сохраняется и может быть вызвано после освобождения объекта (хранилища, кэши)
  • В протоколах с weak-делегатами

Используйте [unowned self] когда:

  • Работаете с анимациями UIView/UIKit
  • Замыкание точно выполнится в пределах жизненного цикла объекта
  • Объект никогда не будет освобожден раньше замыкания (синглтоны, root-объекты)

Современные подходы и best practices

// 1. Комбинирование weak self с guard let
func loadData() {
    apiService.fetchData { [weak self] result in
        guard let self = self else { return }
        
        // Использование self после безопасного захвата
        self.handle(result)
    }
}

// 2. Использование capture list с другими параметрами
func configureCell(for item: Item) {
    database.loadDetails(for: item.id) { [weak self, item] details in
        guard let self = self else { return }
        self.display(item: item, with: details)
    }
}

// 3. Swift 5.7+ - явное указание weak в capture list
func modernApproach() {
    Task { [weak self] in
        guard let self else { return }
        await self.processData()
    }
}

Ключевые выводы

  1. Всегда используйте capture lists ([weak self] или [unowned self]) в escaping-замыканиях, которые захватывают self
  2. По умолчанию выбирайте [weak self] — это безопаснее и предсказуемее
  3. [unowned self] используйте осознанно только когда полностью уверены в жизненном цикле
  4. Проверяйте deinit в тестах, чтобы убедиться в отсутствии циклов
  5. Инструменты отладки: Instruments Leaks, Memory Graph Debugger помогут находить проблемы

Правильное использование захвата self — это не только вопрос предотвращения утечек памяти, но и архитектурный паттерн, который делает код более предсказуемым и поддерживаемым. Выбор между weak и unowned должен быть осознанным решением, основанным на понимании жизненных циклов объектов в вашем приложении.