Почему вызов deinit не означает мгновенное освобождение памяти?

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

Ответ

Вызов deinit и фактическое освобождение памяти — это два разных этапа в жизненном цикле объекта. deinit вызывается, когда счетчик ссылок достигает нуля, но память может быть освобождена позже из-за системных оптимизаций.

Этапы освобождения объекта:

  1. Счетчик ссылок → 0 — ARC определяет, что объект больше не нужен
  2. Вызов deinit — выполняется пользовательская логика очистки
  3. Освобождение памяти — система возвращает память в пул

Почему есть задержка:

  1. Autorelease pools — объекты могут быть добавлены в autorelease pool:

    func createTemporary() -> NSString {
    return NSString(string: "temp") // Может быть autoreleased
    }
    // deinit вызовется при дренировании pool, не сразу
  2. Оптимизации рантайма — система может группировать операции освобождения для эффективности

  3. Слабые ссылки и side tables — для weak-ссылок объект переходит в "зомби"-состояние:

    
    class MyClass {
    deinit { print("Deinit called") }
    }

var strongRef: MyClass? = MyClass() weak var weakRef = strongRef

strongRef = nil // deinit вызывается // Но weakRef еще какое-то время указывает на "зомби"-объект // Память освобождается после обнуления weak-ссылок


4. **Пул памяти (memory pool)** — система может отложить возврат памяти ОС для повторного использования

**Практический пример:**
```swift
class ResourceHolder {
    let id: Int

    init(id: Int) {
        self.id = id
        print("Init (id)")
    }

    deinit {
        print("Deinit (id) called")
        // Но память может быть освобождена позже
    }
}

func test() {
    autoreleasepool {
        let obj = ResourceHolder(id: 1)
        print("Object created")
    } // deinit вызовется здесь
    print("Pool drained")
    // Память может быть освобождена здесь или позже
}

test()

Важно: Не полагайтесь на мгновенное освобождение памяти в deinit. Для критичных ресурсов (файлы, сетевые соединения) используйте явное закрытие до deinit.