Какие типы ссылок существуют в Swift для управления памятью (ARC)?

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

Ответ

Swift использует автоматический подсчет ссылок (ARC) и предоставляет три типа ссылок для управления владением объектами и предотвращения циклов сильных ссылок (strong reference cycles):

Тип ссылки Влияние на счетчик ссылок (retain count) Описание Объявление
strong (сильная) Увеличивает на 1. Ссылка по умолчанию. Объект удерживается в памяти, пока существует хотя бы одна сильная ссылка на него. var object: SomeClass
weak (слабая) Не увеличивает. Не удерживает объект. Автоматически становится nil, когда объект освобождается. Всегда должна быть optional (var). weak var delegate: SomeDelegate?
unowned (бесхозная) Не увеличивает. Предполагает, что объект будет существовать на протяжении всей жизни ссылки. Не является optional. При обращении к освобожденному объекту вызывает краш (runtime error). unowned let parent: ViewController

Когда использовать?

  1. strong: Для обычных отношений «владелец-объект». Большинство ссылок в коде являются сильными.

  2. weak: Для ссылок, которые могут быть nil (опциональные связи), особенно в случаях обратных ссылок, чтобы разорвать цикл.

    • Типичный пример: Делегат (Delegate).

      protocol MenuDelegate: AnyObject { /* ... */ }
      
      class MenuViewController {
          weak var delegate: MenuDelegate? // Слабая ссылка!
      }
      
      class MainViewController: MenuDelegate {
          let menu = MenuViewController()
      
          init() {
              menu.delegate = self // MainViewController не удерживается menu
          }
      }
    • Также используется для IBOutlet (связей из Storyboard), если они не являются корневыми view.
  3. unowned: Используется, когда время жизни ссылающегося объекта короче или равно времени жизни объекта, на который ссылаются. Гарантия существования лежит на архитектуре приложения.

    • Типичный пример: Замыкание (closure), захватывающее self, когда self гарантированно не станет nil раньше вызова замыкания.

      class NetworkManager {
          var onComplete: (() -> Void)?
      
          func fetchData(completion: @escaping () -> Void) {
              self.onComplete = completion
              // ... асинхронная операция
          }
      
          func handleResponse() {
              onComplete?() // Замыкание будет вызвано
          }
      }
      
      class DataHandler {
          let manager = NetworkManager()
          private var data: String = ""
      
          func load() {
              // Используем unowned, т.к. DataHandler живет дольше, чем операция fetchData,
              // и не будет деинициализирован до вызова замыкания.
              manager.fetchData { [unowned self] in
                  self.data = "Loaded" // Краш, если self был освобожден!
              }
          }
      }
    • Более безопасная альтернатива unownedweak с optional binding.
      manager.fetchData { [weak self] in
          guard let self = self else { return }
          self.data = "Loaded" // Безопасно
      }

Ключевое правило: Используйте weak для опциональных обратных ссылок (делегаты, замыкания) и unowned только когда абсолютно уверены в времени жизни объектов. В сомнительных случаях предпочитайте weak.