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

Как установить ссылки, чтобы произошел Retain Cycle?

1.7 Middle🔥 151 комментариев
#Управление памятью

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

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

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

Как создать Retain Cycle в Swift

Retain Cycle (циклическая зависимость) — это ситуация в управлении памятью, когда два или более объектов держат сильные ссылки (strong references) друг на друга, образуя замкнутый цикл. Это приводит к тому, что объекты никогда не будут освобождены системой автоматического подсчета ссылок (ARC), вызывая утечку памяти (memory leak).

Основные механизмы создания Retain Cycle

1. Два объекта, ссылающиеся друг на друга через свойства

class Person {
    var apartment: Apartment?
    
    init() {
        print("Person создан")
    }
    
    deinit {
        print("Person освобожден")
    }
}

class Apartment {
    var tenant: Person?
    
    init() {
        print("Apartment создан")
    }
    
    deinit {
        print("Apartment освобожден")
    }
}

func createRetainCycle() {
    let person = Person()
    let apartment = Apartment()
    
    // Создание циклической зависимости
    person.apartment = apartment
    apartment.tenant = person
}

createRetainCycle()
// После выполнения функции объекты person и apartment
// не будут освобождены, так как ссылаются друг на друга

2. Замыкания (Closures), захватывающие self

Замыкания в Swift по умолчанию захватывают ссылки на используемые внутри объекты как strong references. Если объект хранит замыкание как свойство, и замыкание захватывает этот объект через self, возникает retain cycle.

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

func testClosureCycle() {
    let manager = DataManager()
    manager.dataProcessor() // Замыкание создано и захватило manager
}
testClosureCycle() // DataManager не будет освобожден

3. Использование делегатов (Delegate) с сильными ссылками

Частая ошибка в архитектуре — установка делегата как сильного свойства вместо слабой ссылки.

protocol ServiceDelegate: AnyObject {
    func serviceCompleted()
}

class Service {
    var delegate: ServiceDelegate? // Ошибка: strong reference!
    
    func start() {
        // ... выполнение операции
        delegate?.serviceCompleted()
    }
    
    deinit {
        print("Service освобожден")
    }
}

class Controller: ServiceDelegate {
    var service: Service?
    
    func setup() {
        service = Service()
        service?.delegate = self // Controller -> Service и Service -> Controller
    }
    
    func serviceCompleted() {
        print("Сервис завершен")
    }
    
    deinit {
        print("Controller освобожден")
    }
}

func testDelegateCycle() {
    let controller = Controller()
    controller.setup()
}
testDelegateCycle() // Ни Controller, ни Service не освобождаются

Как избежать Retain Cycle

Чтобы предотвратить циклические зависимости, используйте следующие подходы:

  1. Слабые ссылки (weak references) — для отношений, где один объект не должен владеть другим (делегаты, обратные вызовы).

    weak var delegate: ServiceDelegate?
    
  2. Безвладельные ссылки (unowned references) — когда объект гарантированно существует во время использования ссылки, но не должен владеть им.

    unowned let parentController: Controller
    
  3. Захват списков в замыканиях — явное указание, как захватывать объекты внутри замыканий.

    lazy var processor: () -> Void = { [weak self] in
        guard let self = self else { return }
        self.processData()
    }
    

Практический пример Retain Cycle в реальном коде

Рассмотрим типичный сценарий в iOS разработке:

class ProfileViewController: UIViewController {
    var userProfile: UserProfile? // UserProfile имеет strong reference на этот контроллер
    
    override func viewDidLoad() {
        super.viewDidLoad()
        userProfile?.refreshData { [weak self] in
            // Без [weak self] здесь возникнет retain cycle
            self?.updateUI()
        }
    }
    
    deinit {
        print("ProfileViewController освобожден")
    }
}

class UserProfile {
    var viewController: ProfileViewController? // Ошибка: сильная ссылка!
    
    func refreshData(completion: @escaping () -> Void) {
        // Запрос данных...
        completion()
    }
    
    deinit {
        print("UserProfile освобожден")
    }
}

В этом примере ProfileViewController и UserProfile образуют замкнутый цикл через сильные ссылки, и ни один из них никогда не будет освобожден даже после закрытия контроллера.

Вывод: Retain Cycle создается сознательно или случайно при установке сильных взаимных ссылок между объектами, особенно в сочетании с замыканиями. Для предотвращения утечек памяти необходимо внимательно анализировать отношения владения и использовать weak или unowned ссылки там, где это соответствует семантике отношений объектов.