1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/CarGuo-GSYFlutterBook

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
Flutter-N6.md 13 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 10.03.2025 00:06 5767d61

Небольшие хитрости Flutter: вы действительно понимаете List и Iterable в Dart?

Сегодня мы рассмотрим интересные аспекты работы с List и Iterable. Возможно, вы скажете, что это просто списки, но на самом деле в Dart эти типы данных имеют свои особенности. Например, иногда можно выполнять операцию map над List, как показано ниже:

var list = ["1", "2", "3", "4", "5"];
var map = list.map((e) {
  var result = int.parse(e) + 10;
  print("######### $result");
  return result;
});

Ответ: ничего не будет выведено, так как методы, такие как map или where, возвращают ленивый объект типа Iterable. Это значит, что они будут выполнены только при обращении к ним.

Например, если вызвать метод toList() или toString(), то будет запущена операция map, которая выведет соответствующие данные.

Новый вопрос: если мы выполним все четыре метода, сколько раз будет выведен лог? Ответ: три раза.

image-20220615164227346

Кроме того, кроме метода isEmpty, остальные три метода снова запустят выполнение метода map.

На самом деле, когда мы выполняем операцию map над List, возвращается ленивый объект типа Iterable. Каждый раз, когда нам требуется получить значение из этого объекта, он заново выполняется, так как результаты предыдущих операций не кэшируются.

Может быть, вам покажется это сложным, поэтому давайте рассмотрим пример от автора библиотеки fast_immutable_collections:- Мы применяем метод where к одному и тому же массиву, чтобы получить объект типа Iterable.

  • Разница заключается в том, что в evenFilterEager дополнительно используется метод .toList().
  • При каждом вызове where счетчики увеличиваются.
  • В конце вызывается метод length трижды, и выводятся значения счетчиков.
var ленивыйСчетчик = 0;
var жадныйСчетчик = 0;

var ленивыйФильтрНечетных = [1, 2, 3, 4, 5, 6, 7].where((i) {
  ленивыйСчетчик++;
  return i % 2 == 0;
});

var жадныйФильтрЧетных = [1, 2, 3, 4, 5, 6, 7].where((i) {
  жадныйСчетчик++;
  return i % 2 == 0;
}).toList();
print("\n\n---------- Инициализация ----------\n\n");

let жадный = removeLessThan10_жадный(removeOdd_жадный(list));
let ленивый = removeLessThan10_ленивый(removeOdd_ленивый(list));

print("\n\n---------- Ленивая вычисляемость ----------\n\n");

print(леневой);

print("\n\n---------- Жадная вычисляемость ----------\n\n");

print(жадный);

Приведенный ниже пример покажет результат выполнения кода:

List<int> удалитьНечетныеЖадный(List<int> источник) {
  return источник.where((i) {
    print("удалитьНечетныеЖадный");
    return i % 2 == 0;
  }).toList();
}

List<int> удалитьМеньше10Жадный(List<int> источник) {
  return источник.where((i) {
    print("удалитьМеньше10Жадный");
    return i >= 10;
  }).toList();
}

Iterable<int> удалитьНечетныеЛениваяВычисляемость(Iterable<int> источник) {
  return источник.where((i) {
    print("удалитьНечетныеЛениваяВычисляемость");
    return i % 2 == 0;
  });
}

Iterable<int> удалитьМеньше10ЛениваяВычисляемость(
    Iterable<int> источник) {
  return источник.where((i) {
    print("удалитьМеньше10ЛениваяВычисляемость");
    return i >= 10;
  });
}
```var список = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];

print("\n\n---------- Инициализация ----------\n\n");

Iterable<int> жадный = удалитьМеньше10_жадный(
    удалитьНечетные_жадный(список));

Iterable<int> нечастаяВычисляемость = удалитьМеньше10_нечастаяВычисляемость(
    удалитьНечетные_нечастаяВычисляемость(список));

print("\n\n---------- Нечастая вычисляемость (ленивый подход) ----------\n\n");

print(нечастаяВычисляемость);

print("\n\n---------- Частая вычисляемость (жадный подход) ----------\n\n");

print(жадный);
```Хотя мы сначала вызываем `print(lazy);`, а затем `print(eager);`, но выводится сначала `removeOdd_eager`, так как в вызовах, связанных с Eager, используется метод `.toList();`. Этот метод выполняется сразу при вызове `removeOdd_eager(list)`, поэтому `removeOdd_eager` полностью выводится перед `removeLessThan10_eager`. В конце значения выводятся при выполнении `print(eager);``.lazy является `Iterable`, поэтому его содержимое выводится только при обращении к нему. Правила вывода следующие: **выводится два раза `removeOdd_lazy`, после чего один раз выводится `removeLessThan10_lazy`**, поскольку каждые две операции удовлетворяют условию `i % 2 == 0;`, что приводит к выполнению `removeLessThan10_lazy` и формированию такой последовательности вывода.

```dart
I/flutter (23298): ---------- Инициализация ----------
I/flutter (23298): 
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeLessThan10_eager
I/flutter (23298): removeLessThan10_eager
I/flutter (23298): removeLessThan10_eager
I/flutter (23298): removeLessThan10_eager
I/flutter (23298): removeLessThan10_eager
I/flutter (23298): removeLessThan10_eager
I/flutter (23298): removeLessThan10_eager
I/flutter (23298): ---------- Ленивая ----------
I/flutter (23298): 
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeLessThan10_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeLessThan10_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeLessThan10_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeLessThan10_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeLessThan10_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeLessThan10_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeLessThan10_lazy
I/flutter (23298): removeOdd_lazy
```I/flutter (23298): (10, 12, 14)  
I/flutter (23298): ---------- Жадный ----------  
I/flutter (23298):   
I/flutter (23298): [10, 12, 14]  
```Не кажется ли вам, что в таких ситуациях `Iterable` усложняет всё? Действительно, при сложной вложенности `Iterable` может сделать логику труднодоступной для поддержки, а официальные рекомендации гласят:

> Из-за возможности многократного прохождения через `Iterable`, использование побочных эффектов внутри итератора не рекомендуется.

А как же использовать `Iterable`? Или где можно применять `Iterable`? На самом деле есть немало сценариев, например:

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

Пример:

```dart
Iterable<int> naturalsFunc() sync* {
  int k = 0;
  // Бесконечный цикл!
  while (true) yield k++;
}

var naturalsIter = naturalsFunc();

print("\n\n---------- Инициализация ----------\n\n");
print("Бесконечный список/итерируемый объект создан, но ещё не выполнен.");
print("\n\n--------------------\n\n");
print("\n\n---------- takeWhile ----------\n\n");
print("С ним можно работать, но требуется метод завершения работы в какой-то момент");
var naturalsUpTo10 = naturalsIter.takeWhile((value) => value <= 10);
print("Натуральные числа до 10: $naturalsUpTo10");
print("\n\n---------- КОНЕЦ ----------\n\n");

На этом этапе вы можете задаться вопросом: **List тоже является Iterable, так почему же он отличается от других Iterable, таких как map, where, expand?**Если мы посмотрим на сам List, то увидим, что это абстрактный объект, являющийся подклассом Iterable. Объекты, реализующие этот интерфейс, обычно являются _GrowableList внутри Dart VM. Структура _GrowableList представлена следующим образом:

image-20220615155944141

Основное различие между List и другими Iterable заключается в том, что:

  • List представляет собой индексируемое множество с фиксированной длиной, поскольку его внутренний ListIterator использует _iterable.length и _iterable.elementAt.
  • Обычные Iterable, такие как MappedIterable после применения map, представляют собой последовательно доступные множества, где MappedIterator используется для последовательного доступа к элементам iterable, игнорируя длину.

image-20220615182431493

Итак, можно сделать вывод: тема данной статьи проста и состоит в том, чтобы быстро освоить различия между List и обычным Iterable, а также понять особенности и области применения позднего вычисления через примеры. Это поможет правильно выбирать тип Iterable и находить проблемы во время разработки. Если у вас остаются какие-либо вопросы, приветствуем ваши комментарии и отзывы.

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/CarGuo-GSYFlutterBook.git
git@api.gitlife.ru:oschina-mirror/CarGuo-GSYFlutterBook.git
oschina-mirror
CarGuo-GSYFlutterBook
CarGuo-GSYFlutterBook
master