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

Ответ

Основные проблемы связаны с автоматическим подсчетом ссылок (ARC) и циклами сильных ссылок:

  1. Retain Cycles (Циклические ссылки) — возникают, когда два или более объекта удерживают друг друга сильными (strong) ссылками, предотвращая их освобождение ARC.

    • Типичные сценарии:
      • Делегаты без weak.
      • Замыкания (closures), захватывающие self.
    • Решение: Использовать weak или unowned ссылки.
    • Пример:
      class ViewController {
          var dataLoader: DataLoader?
      }
      class DataLoader {
          // weak предотвращает цикл: ViewController -> DataLoader -> weak -> ViewController
          weak var delegate: ViewController?
      }
  2. Сильное захвата self в замыканиях. Замыкания по умолчанию захватывают переменные сильными ссылками.

    • Решение: Использовать список захвата [weak self] или [unowned self].
    • Пример:
      networkService.fetchData { [weak self] result in
          // self стал Optional из-за weak
          guard let self = self else { return }
          self.updateUI(with: result)
      }
  3. Непреднамеренное удержание объектов (Unintentional Retention). Объекты могут жить дольше необходимого из-за сильных ссылок в коллекциях (например, кэш в виде обычного Dictionary), глобальных переменных или синглтонах.

    • Решение: Использовать NSCache (автоматически очищает память под давлением), тщательнее управлять жизненным циклом.
  4. Утечки в UIViewController. Контроллер не освобождается после закрытия, часто из-за:

    • Замыканий (см. п.2).
    • Наблюдателей NotificationCenter, не отписанных в deinit.
    • Таймеров (Timer), не инвалидированных.
    • Решение: Всегда отписываться и останавливать активность в deinit.
      deinit {
          NotificationCenter.default.removeObserver(self)
          myTimer?.invalidate()
      }

Ответ 18+ 🔞

А, ну вот, классика, блядь! Подходишь ты к этому ARC, такой весь из себя умный, думаешь — ну, автоматика же, зашибись! А он тебе, сука, такие сюрпризы подкидывает, что волосы дыбом встают, ёпта!

Первое и главное — это циклические ссылки, они же retain cycles, блядь. Представь: два мужика в баре схватили друг друга за грудки и орут «Ты меня отпусти! — Нет, ты меня отпусти!». И стоят так до скончания времён, потому что оба упёртые, как ослы. В коде так же: два объекта держат друг друга за strong ссылки, и оба в память упёрлись — ни один не сдохнет. Пипец полный.

Где это вылезает? Да везде, блядь! Самые любимые грабли:

  1. Делегаты, которые забыли сделать weak. Ну, классика жанра! Контроллер создал сервис, сервис взял контроллер делегатом сильной ссылкой — и поехали. Контроллер не может умереть, потому что сервис его держит, а сервис не может умереть, потому что контроллер его держит. Замкнутый круг, ебаный в рот. Решение — одно слово: weak. Сделал делегата слабым — и всё, магия, цикл порвался.

    class DataLoader {
        // Вот эта вот приписка 'weak' — она как отвёртка, которая откручивает эту ебучую хватку.
        weak var delegate: ViewController?
    }
  2. Замыкания, эти хитрожопые ублюдки. Они по умолчанию хватают всё, что видят, мёртвой хваткой, включая self. Отправил ты сетевой запрос, передал кложур, а там внутри self.updateUI() — всё, пиши пропало. self теперь в плену у этого кложура до конца его дней. А кложур живёт, пока запрос не выполнится. И если экран закрыли, а запрос ещё идёт — экран в памяти болтается, как призрак, блядь. Решение? Список захвата, мать его! [weak self] — и сразу жизнь налаживается. Только не забудь потом этого self развернуть, а то будешь обращаться к nil и удивляться, почему ничего не работает.

    networkService.fetchData { [weak self] result in
        // Сначала стряхни с себя этот weak, сделай сильным на время.
        guard let self = self else { return } // Или просто 'self?' в новых свифтах, похуй.
        self.updateUI(with: result) // Теперь можно работать.
    }
  3. Непреднамеренное удержание — это вообще песня. Завёл ты себе кэшик в виде простого Dictionary, думаешь — а чё, удобно. А он, сука, всё, что в него положили, держит сильнее бульдога. Картинки там, данные. Память упёрлась в потолок, система начинает убивать твоё приложение, а ты чешешь репу. Решение? NSCache, дружок! Умная штука, которая под давлением сама почистит мусор. Или сам вовремя чисти, не будь свиньёй.

  4. А контроллеры (UIViewController) — это отдельная эпопея, ёперный театр! Закрыл экран, а он не деинитится. Сидит в памяти, как маньяк в шкафу. Почему?

    • Кложуры — уже обсудили, блядь.
    • Наблюдатели NotificationCenter. Подписался в viewDidLoad, а отписаться забыл. Теперь этот труп будет вечно слушать все уведомления в системе. В deinit надо орать: NotificationCenter.default.removeObserver(self)!
    • Таймеры (Timer). Запустил, а invalidate() не вызвал. Он и после смерти экрана тикает, как заведённый. Останови его в deinit!
      deinit {
      // Вот это вот — святое. Уборка за собой.
      NotificationCenter.default.removeObserver(self)
      myTimer?.invalidate()
      print("Наконец-то (self) сдох, ура!") // Для душевного спокойствия.
      }

Короче, суть в чём, блядь? ARC — он не волшебник, он уборщик. Он убирает только тот мусор, на который никто не показывает пальцем. А если ты намусорил и сам на свой мусор указал сильной ссылкой — извини, дружок, это теперь твой персональный артефакт в памяти до перезагрузки приложения. Думай головой, используй weak/unowned, чисть за собой — и будет тебе счастье. А не будет — welcome to Leaks Instruments, ебать твою в сраку.