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

OSCHINA-MIRROR/CarGuo-GSYFlutterBook

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

В этой статье мы рассмотрим интересное явление в Dart-коде с помощью простого примера.

Мы знаем, что во всех объектах Dart есть классы, даже базовые типы данных, такие как int, double и bool.

Когда вы выполняете операции над этими классами, такими как +, -, * и /, фактически вызываются методы операторов этих классов, которые возвращают новый объект типа num.

Иллюстрация

Эти операторы реализуются через виртуальную машину (VM), а сам Dart-код представляет собой текстовый файл, который затем компилируется в двоичный код для выполнения.

Иллюстрация

Пример основан на версии Dart 2.12.3

Что же мы хотим обсудить?

Давайте рассмотрим следующий код:

class MyHomePage extends StatelessWidget {
  var images = ["RRR", "RRR", "RRR"];
  @override
  Widget build(BuildContext context) {
    List<Widget> contents = [];
    int idx = 0;
    for (var imgUrl in images) {
      contents.add(InkWell(
          onTap: () {
            print("######## $idx");
          },
          child: Container(
              height: 100,
              width: 100,
              color: Colors.red,
              child: Text(imgUrl))));
      idx++;
    }
    return Scaffold(
        appBar: AppBar(),
        body: Center(
            child: Column(children: [...contents])));
  }
}
  • Вопрос заключается в том, что будет выводиться при нажатии на эти три Inkwell?

  • **Ответ состоит в том, что будет выведено число 3.**Почему это происходит? Давайте посмотрим на логику после компиляции этого кода. Как видно из приведённого ниже кода, функция print всегда указывает на указатель int* переменной idx. Когда вы кликаете, то в конечном итоге выводится последнее значение переменной idx.

    @#C475
    метод build(fra2::BuildContext* context)  fra2::Widget* {
      core::Список<fra2::Widget*>* contents = core::_GrowableList::<fra2::Widget*>(0);
      core::int* idx = 0;
      {
        core::Итератор<core::Строка*>* :sync-for-iterator = this.{main::MyHomePage::images}.{core::Итерируемый::iterator};
        for (; :sync-for-iterator.{core::Итератор::перейтиКСледующемУзлу}(); ) {
          core::Строка* imgUrl = :sync-for-iterator.{core::Итератор::текущийЭлемент};
          {
            [@vm.call-site-attributes.metadata=receiverType:dart.core::List<library package:flutter/src/widgets/framework.dart::Widget*>*] contents.{core::Список::добавить}(новый ink5::InkWell::(onTap: ()  Null {
              core::печать("######## ${idx}");
            }, child: новый con7::Container::(высота: 100.0, ширина: 100.0, цвет: #C40086, $creationLocationd_0dea112b090073317d4: #C66610), $creationLocationd_0dea112b090073317d4: #C66614));
            idx = idx.{core::число::+}(1);
          }
        }
      }

В данном примере, когда вызывается функция print, она выводит текущее значение указателя idx. При каждом нажатии кнопки значение idx увеличивается на единицу, поэтому при последнем нажатии будет выведено последнее значение этой переменной.Примечание: В данном примере были использованы специфичные термины для перевода кода на русский язык, сохраняя структуру и синтаксис оригинального кода.А что если нам нужно выводить индекс каждого InkWell отдельно?

Как показано ниже в цикле for, мы добавляем параметр index, который присваивает значение idx при каждом прохождении цикла. Таким образом, при нажатии на каждый InkWell будет выведен соответствующий ему индекс.

class MyHomePage extends StatelessWidget {
  var images = ["RRR", "RRR", "RRR"];
  
  @override
  Widget build(BuildContext context) {
    List<Widget> contents = [];
    int idx = 0;

    for (var imgUrl in images) {
      int index = idx;
      contents.add(InkWell(
          onTap: () {
            print("######## $index");
          },
          child: Container(
            height: 100,
            width: 100,
            color: Colors.red,
            child: Text(imgUrl),
          )
      ));
      
      idx++;
    }

    return Scaffold(
      appBar: AppBar(),
      body: Center(
          child: Column(
            children: [
              ...contents,
            ],
          )
      ),
    );
  }
}

Почему так?

Давайте рассмотрим скомпилированный Dart-код:c++ @#C475 метод build(fra2::BuildContext* context) → fra2::Widget* { core::List<fra2::Widget*>* contents = core::_GrowableList::•<fra2::Widget*>(0); core::int* idx = 0; { core::Iterator<core::String*>* :sync-for-iterator = this.{main::MyHomePage::images}.{core::Iterable::iterator}; для (; :sync-for-iterator.{core::Iterator::moveNext}(); ) { core::String* imgUrl = :sync-for-iterator.{core::Iterator::current}; { core::int* index = idx; [@vm.call-site-attributes.metadata=receiverType:dart.core::List<library package:flutter/src/widgets/framework.dart::Widget*>*] contents.{core::List::add}(new ink5::InkWell::•(onTap: () → Null { core::print("######## ${index}"); }, child: new con7::Container::•(высота: 100.0, ширина: 100.0, цвет: #C40086, $creationLocationd_0dea112b090073317d4: #C66610), $creationLocationd_0dea112b090073317d4: #C66614)); idx = idx.{core::num::+}(1); } } } } Здесь видно, как используется оператор = для создания нового объекта index. В результате, когда вызывается метод print, он выводит последнее сохранённое значение idx.

Итак, вот другой подход.

Как показано ниже в коде, InkWell помещается внутрь функции getItem, которая его возвращает. Индекс (index) передается через аргумент функции. После запуска программы можно заметить, что при нажатии на соответствующий InkWell выводится соответствующий индекс.

class MyHomePage extends StatelessWidget {
  var images = ["RRR", "RRR", "RRR",];

  @override
  Widget build(BuildContext context) {
    final List<Widget> contents = [];
    int idx = 0;

    getItem(int index, String imgUrl) {
      return InkWell(
        onTap: () {
          print('######## $index');
        },
        child: Container(
          height: 100,
          width: 100,
          color: Colors.red,
          child: Text(imgUrl),
        ),
      );
    }

    for (final String imgUrl in images) {
      contents.add(getItem(idx, imgUrl));
      idx++;
    }

    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Column(
          children: [...contents],
        ),
      ),
    );
  }
}

Почему так?Давайте рассмотрим сгенерированный код, как показано ниже. В действительности каждый раз значение idx передаётся в функцию getItem через вызов getItem.call(idx). Значение index внутри функции getItem ссылается на этот аргумент. При следующем вызове снова передаётся соответствующее значение idx. Таким образом, принцип работы аналогичен случаю выше, поэтому при каждом нажатии выводится соответствующий индекс.

    @#C475
    метод build(fra2::BuildContext* context)  fra2::Widget* {
      core::Список<fra2::Widget*>* contents = core::_GrowableList::<fra2::Widget*>(0);
      core::int* idx = 0;
      функция getItem(core::int* index)  ink5::InkWell* {
        return new ink5::InkWell::(onTap: ()  Null {
          core::print("######## ${index}");
        }, child: new con7::Container::(высота: 100.0, ширина: 100.0, цвет: #C40086, $creationLocationd_0dea112b090073317d4: #C66610), $creationLocationd_0dea112b090073317d4: #C66614);
      }
      {
        core::Итератор<core::Строки::*>* :sync-for-iterator = this.{main::MyHomePage::images}.{core::Итерируемый::iterator};
        для (; :sync-for-iterator.{core::Итератор::moveNext}(); ) {
          core::Строки::* imgUrl = :sync-for-iterator.{core::Итератор::current};
          {
            [@vm.call-site-attributes.metadata=receiverType:dart.core::List<library package:flutter/src/widgets/framework.dart::Widget*>*] contents.{core::Список::add}([@vm.call-site-attributes.metadata=receiverType:library package:flutter/src/material/ink_well.dart::InkWell* Функция(dart.core::int*)*] getItem.call(idx));
            idx = idx.{core::число::+}(1);
          }
        }
      }
      

**Итак, давайте рассмотрим ещё один вариант реализации.**Как показано ниже, можно использовать самый базовый цикл for, чтобы добавить InkWell и вывести значение idx.

class MyHomePage extends StatelessWidget {
  var images = ["RRR", "RRR", "RRR"];

  @override
  Widget build(BuildContext context) {
    List<Widget> contents = [];
    for (int idx = 0; idx < images.length; idx++) {
      contents.add(InkWell(
          onTap: () {
            print("######## $idx");
          },
          child: Container(
              height: 100,
              width: 100,
              color: Colors.red,
              child: Text(images[idx]))));
    }
    return Scaffold(
        appBar: AppBar(),
        body: Center(
            child: Column(children: [...contents])));
  }
}

Результат будет следующим: при нажатии на соответствующий InkWell выводится соответствующее значение индекса.

Почему так происходит?

Давайте внимательно посмотрим на сгенерированный код после компиляции. Видим, что выводятся значения idx. Почему это работает правильно?

Основное отличие заключается в том, где объявлен idx.

    @#C475
    method build(fra2::BuildContext* context)  fra2::Widget* {
      core::List<fra2::Widget*>* contents = core::_GrowableList::<fra2::Widget*>(0);
      for (core::int* idx = 0; idx.<(this.{main::MyHomePage::images}.length); idx = idx.+)(1)) {
        [@vm.call-site-attributes.metadata=receiverType:dart.core::List<library package:flutter/src/widgets/framework.dart::Widget*>*] contents.<>(new ink5::InkWell::(onTap: ()  Null {
          core::print("######## ${idx}");
        }, child: new con7::Container::(height: 100.0, width: 100.0, color: #C40086, child: new text::Text::(this.{main::MyHomePage::images}[idx], $creationLocationd_0dea112b090073317d4: #C66607), $creationLocationd_0dea112b090073317d4: #C66613), $creationLocationd_0dea112b090073317d4: #C66617));
      }
```Теперь попробуем снова поместить `idx` за пределы цикла `for`. При тестировании нажатия мыши заметим, что все выводы будут иметь значение `3`.

class MyHomePage extends StatelessWidget { var images = ["RRR", "RRR", "RRR"];

  @override
  Widget build(BuildContext context) {
    List<Widget> contents = [];
    int idx = 0;
    for (; idx < images.length; idx++) {
      contents.add(InkWell(
          onTap: () {
            print("######## $idx");
          },
          child: Container(
            height: 100,
            width: 100,
            color: Colors.red,
            child: Text(images[idx]),
          )));
    }
    return Scaffold(
        appBar: AppBar(),
        body: Center(
            child: Column(
          children: [
            ...contents,
          ],
        )));
  }
}
```Это почему?

Просмотрите скомпилированный код, единственное различие — это местоположение объявления `core::int* idx`. Но какова же причина этого?

```c++
    @#C475
    метод build(fra2::BuildContext* контекст) → fra2::Widget* {
      core::Список<fra2::Widget*>* содержимое = core::_GrowableList::•<fra2::Widget*>(0);
      core::int* idx = 0;
      для (; idx.{core::число::<}(этот.{главное::МойHomeAsпэдж::изображения}.{core::Список::длина}); idx = idx.{core::число::+}(1)) {
        [@vm.call-site-attributes.metadata=receiverType:dart.core::Список<библиотека пакет:flutter/src/widgets/framework.dart::Widget*>*] содержимое.{core::Список::добавить}(новый ink5::InkWell::•(onTap: () → Null {
          core::печать("######## ${idx}");
        }, ребенок: новый con7::Контейнер::•(высота: 100.0, ширина: 100.0, цвет: #C40086, ребенок: новый text::Текст::•(этот.{главное::МойHomeAsпэдж::изображения}.{core::Список::[]}(idx), $creationLocationd_0dea112b090073317d4: #C66607), $creationLocationd_0dea112b090073317d4: #C66613), $creationLocationd_0dea112b090073317d4: #C66617));
      }
    }
```Итак, потому что `onTap` выводит параметры при нажатии, а для `for (core::int* idx = 0;)`, область видимости `idx` ограничивается циклом `for`. Поэтому в скомпилированной версии внутри `onTap` должен существовать соответствующий объект для хранения значения.

Что касается `core::int* idx`, объявленного вне цикла `for`, все `onTap` внутри цикла могут обращаться к этому адресу, поэтому при каждом нажатии выводится одно и то же значение `idx`.

Что касается логики такого поведения, более глубокое исследование выполнено не было (лень), **можно предположить, что это связано с оптимизациями компилятора относительно параметров, объявленных за пределами цикла и внутри него**.

Теоретически, это должно относиться к захвату переменных:

* Для глобальных переменных, они не захватываются, доступ осуществляется через глобальные переменные.
* Для локальных переменных, автоматически захватывается значение, передача происходит по значению.

Наконец, если вы хотите просмотреть содержимое файла `dill`, можно использовать команду `xxd` на macOS:

```
xxd /путь/к/файлу/.dart_tool/flutter_build/bf7ed8e7e7b3e64f28f0af8a89a29ca9/app.dill
```Также можно использовать команду `dump_kernel.dart` (расположена в директории `/путь/к/sdk/pkg/vm`) для создания файла `app.dill.txt` для просмотра, например, чтобы сравнить различия между `final` и `const` после компиляции.

```bash
dart dump_kernel.dart /путь/к/проекту/.dart_tool/flutter_build/идентификатор_билда/app.dill /путь/к/проекту/.dart_tool/flutter_build/идентификатор_билда/app.dill.txt
```

Опубликовать ( 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