Что такое Garbage Collector в Dart?

Ответ

В Dart Garbage Collector (GC) — это механизм автоматического управления памятью, который освобождает память, занятую объектами, ставшими недостижимыми. Dart использует generational garbage collection с двумя основными поколениями:

  • Молодое поколение (Young Generation): Использует быстрый алгоритм scavenger для очистки короткоживущих объектов (например, временных переменных в методе build). Сборка мусора здесь происходит часто и быстро.
  • Старое поколение (Old Generation): Для долгоживущих объектов используется более медленный алгоритм (mark-sweep или mark-compact).

Практическое влияние на Flutter:

  • Микрофризы (jank): Длительная сборка мусора в основном потоке может вызвать пропуск кадров. Это критично для анимаций и скроллинга.
  • Оптимизации:
    • Избегайте создания лишних объектов в часто вызываемых методах (build, didUpdateWidget, анимационных листенерах). Используйте const конструкторы для виджетов.
    • Для отложенной инициализации тяжёлых объектов используйте late final.
    • Управляйте подписками (StreamSubscription, Listenable) и явно отменяйте их в dispose, чтобы объекты стали недостижимыми раньше.

Пример плохой и хорошей практики:

// Плохо: Создаёт новый список на каждом вызове build
Widget build(BuildContext context) {
  List<Widget> items = []; // Новый объект каждый кадр
  for (var i = 0; i < 100; i++) {
    items.add(Text('Item $i'));
  }
  return ListView(children: items);
}

// Лучше: Кэширование списка (если данные статичны)
class MyWidget extends StatelessWidget {
  final List<Widget> _cachedItems = List.generate(
    100,
    (i) => Text('Item $i'),
  );

  @override
  Widget build(BuildContext context) {
    return ListView(children: _cachedItems);
  }
}

Ответ 18+ 🔞

Ага, слушай, про сборщик мусора в Dart. Ну, это такая штука, которая сама за тобой убирает, как мама в детстве, когда ты игрушки по всей комнате разбросал и забыл. Только тут не игрушки, а объекты в памяти, которые уже никому не нужны.

Вот смотри, как оно работает, ёпта. У них там система, прямо как в армии: есть молодое поколение и старое поколение.

  • Молодое поколение — это как новобранцы-срочники. Живут недолго, создал переменную в методе build — и через милисекунду её уже нет. Уборка там быстрая, частенько происходит, по принципу «собрал-выкинул». Никакой мороки.
  • А вот старое поколение — это уже старослужащие, офицеры. Тут всё серьёзнее. Объекты живут долго, и чтобы их убрать, нужно провести полноценную инвентаризацию: пометить всех, кто ещё нужен, а потом вычистить тех, кто не нужен. Процесс, понятное дело, подольше.

А теперь самое важное, на что это влияет в Flutter:

Представь, у тебя плавная анимация идёт, или ты листаешь ленту, а тут БАЦ — и сборщик мусора в старом поколении решил поработать. Он может на пару кадров всё подвесить! Это и есть тот самый jank, микрофриз, от которого у пользователей волосы дыбом встают. Терпения ноль ебать у людей, если приложение дёргается.

Так что же делать, чтобы не было пиздеца?

Главная мысль — не создавай овердохуища мусора там, где это не нужно. Особенно в тех методах, которые вызываются 60 раз в секунду.

  • Избегай как огня создания кучы новых объектов в build, в анимационных коллбеках или в didUpdateWidget. Зачем каждый раз новый список городить, если можно один создать и переиспользовать?
  • Вот смотри, классический косяк и как его исправить:
// Пиздопроебищный код. Каждый раз, когда экран перерисовывается,
// ты создаёшь новый список. Сборщик потом с ума сойдёт, убирая эту кучу.
Widget build(BuildContext context) {
  List<Widget> items = []; // Новый объект каждый кадр, ядрёна вошь!
  for (var i = 0; i < 100; i++) {
    items.add(Text('Item $i'));
  }
  return ListView(children: items);
}

// А вот уже умный подход. Создали список один раз и кэшировали.
// Теперь в `build` мы его просто возвращаем, не порождая новый мусор.
// GC доволен, фпс стабильный, все живы.
class MyWidget extends StatelessWidget {
  final List<Widget> _cachedItems = List.generate(
    100,
    (i) => Text('Item $i'),
  );

  @override
  Widget build(BuildContext context) {
    return ListView(children: _cachedItems);
  }
}

Ещё лайфхак: для тяжёлых объектов, которые инициализируются один раз, юзай late final. И подписки всякие на стримы или анимации — обязательно отменяй в dispose(). А то останутся они висеть в памяти, как призраки, и сборщик будет на них коситься, но тронуть не посмеет. Доверия ебать ноль у него к таким недобитым ссылкам.

Короче, суть в том, чтобы думать головой. Не плоди сущности без нужды, и тогда GC не будет тебе палки в колёса в самый ответственный момент вставлять. Всё просто, как три копейки.