В этой статье мы рассмотрим интересное явление в 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 )