Как работает управление памятью (Memory Management) в iOS (Swift)?

«Как работает управление памятью (Memory Management) в iOS (Swift)?» — вопрос из категории Управление памятью, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Управление памятью в iOS на Swift основано на механизме ARC (Automatic Reference Counting). ARC автоматически отслеживает количество сильных ссылок на каждый экземпляр класса и освобождает память, когда счетчик ссылок становится равным нулю.

Типы ссылок:

  1. Strong (Сильная, по умолчанию): Увеличивает счетчик ссылок (retain count) на 1. Объект живет, пока на него есть хотя бы одна сильная ссылка.
  2. Weak (Слабая): Не увеличивает счетчик ссылок. Ссылка автоматически становится nil, когда объект освобождается. Всегда объявляется как var и optional.
    weak var delegate: MyDelegate?
  3. Unowned (Бесхозная): Аналогична weak, но не является optional. Предполагается, что объект, на который ссылаются, никогда не будет освобожден раньше ссылающегося объекта. При нарушении этого ведет к крашу.
    unowned let parentController: UIViewController // parentController должен пережить этот объект

Главная проблема: Циклические сильные ссылки (Retain Cycle) Возникает, когда два объекта удерживают друг друга сильными ссылками, не давая ARC освободить их.

Пример и решение:

class Person {
    var apartment: Apartment? // Сильная ссылка
    deinit { print("Person освобожден") }
}

class Apartment {
    weak var tenant: Person? // СЛАБАЯ ссылка разрывает цикл
    deinit { print("Apartment освобожден") }
}

var john: Person? = Person()
var unit4A: Apartment? = Apartment()

john?.apartment = unit4A // Apartment.refCount = 1 (от john)
unit4A?.tenant = john    // Person.refCount = 1 (от unit4A, но weak!)

john = nil    // Person.refCount -> 0, Person освобождается
unit4A = nil  // Apartment.refCount -> 0, Apartment освобождается
// Оба deinit будут вызваны.

Типичные сценарии и решения:

  • Делегаты: Всегда объявляйте как weak var.
  • Замыкания (Closures): Используйте список захвата [weak self] или [unowned self], если замыкание может создать цикл с self.
    networkService.completion = { [weak self] result in
        self?.handle(result) // self — optional
    }
  • Инструменты для отладки: Используйте Debug Memory Graph в Xcode и инструмент Leaks в Instruments для поиска циклов.