Опубликован: 02.04.2013 | Доступ: свободный | Студентов: 987 / 20 | Длительность: 20:18:00
Специальности: Программист
Лекция 4:

Анатомия приложения и навигация по страницам

События WinRT и removeEventListener

Обычной практикой в HTML и JavaScript, особенно для веб-сайтов, является то, что мы уже делали в данном учебном курсе. Мы вызывали addEventListener для задания обработчика события или просто присваивали обработчик события свойству on<event> какого-либо объекта. Часто эти обработчики просто объявляют в качестве встроенных анонимных функций:

var myNumber = 1;
element.addEventListener(<event>, function (e) { myNumber++; } );

Благодаря особым правилам обзора данных, действующим в JavaScript, область видимости подобной анонимной функции совпадает с окружающим её кодом, что позволяет коду внутри этой функции ссылаться на локальные переменные наподобие myNumber в вышеприведенном коде.

Для того, чтобы убедиться, что подобные переменные доступны данной анонимной функции, когда она позже будет вызвана в качестве обработчика события, JavaScript-движок создаёт замкнутое выражение (closure), структуру данных, описывающую локальные переменные, доступные данной функции. Обычно замкнутое выражение представляет собой небольшой участок памяти, но в зависимости от кода внутри обработчика события, замкнутое выражение может включить в себя всё глобальное пространство имён, что потребует весьма значительного выделения памяти!

Каждое такое замкнутое выражение увеличивает объем памяти или рабочий набор (working set) приложения, поэтому хорошо поддерживать минимальный объем подобных данных. Например, объявление отдельных именованных функций, которые имеют собственную область видимости - уменьшит размер необходимых замкнутых выражений.

Более важно, нежели уменьшение размера замкнутых выражений, это уверенность в том, что прослушиватели событий сами по себе и, связанные с ними замкнутые выражения соответствующим образом освобождают выделенную им память.

Обычно это не то, о чём вам нужно думать. Когда объекты, такие, как HTML-элементы, уничтожаются, как в случае, когда элементы страницы выгружаются из DOM, связанные с ними прослушиватели автоматически удаляются и ресурсы, занятые замкнутыми выражениями так же освобождаются. Однако, в приложениях для Магазина Windows, написанных на HTML и JavaScript есть и другие источники событий, для которых приложение может добавить прослушиватели в то время, как данные объекты никогда не уничтожаются. Это могут быть объекты из WinJS, объекты из WinRT, window и document. Данные Прослушиватели должны быть соответствующим образом очищены, в противном случае приложение будет иметь утечки памяти (память, которая выделена, но никогда не освобождается при операции сборки мусора).

Особого внимания требуют события, которые исходят от объектов WinRT. Из-за сущности уровня проекции, который делает WinRT доступным в WinJS, WinRT ограничивается хранением ссылок на обработчики событий JavaScript (известных так же как делегаты (delegates)), пока замкнутые выражения JavaScript хранят ссылки на некоторые объекты WinRT. В результате наличия подобных перекрестных ссылок, эти замкнутые выражения могут никогда не быть очищенными.

Это не проблема, помните, если приложение всегда прослушивает конкретные события. Например, события suspending и resuming - это те события, которые приложение обычно прослушивает в течение всего времени жизни приложения, в итоге, любые связанные с ними выделения памяти будут очищены при завершении работы приложения. То же самое справедливо для большинства прослушивателей, которые вы можете добавить для событий объектов window и document, которые постоянно существуют во время жизни приложения.

Утечки памяти, однако, возникают, когда приложение прослушивает события объектов WinRT лишь временно и пренебрегает непосредственным вызовом removeEventListener, или когда приложение вызывает addEventListener для одного и того же события несколько раз (в таком случае вы получите несколько замкнутых выражений). В случае с элементом управления страницы, как обсуждалось в данной лекции, обычная практика заключается в вызове addEventListener в методе ready страницы для некоторого WinRT-объекта. Когда вы делаете это, убедитесь в том, что есть соответствующий данному вызову вызов removeEventListener в методе страницы unload, который освободит ресурсы, занятые замкнутым выражением. Я сделал это в примере HereMyAm3d с datarequested, для ясности.

В этом учебном курсе события WinRT, на которые вам следует обратить внимание, выделены специальным цветом, как datarequested (за исключением текста, который является гиперссылкой). Это напоминание для проверки того, нужен ли явный вызов removeEventListener. Опять же, если вы всегда прослушиваете событие, удалять прослушиватель не нужно, но если вы добавили его, когда загружали элемент управления страницы, вам практически гарантированно понадобится выполнить этот дополнительный вызов. Особенно обратите внимание на то, что примеры не обязательно уделяют внимание этой особенности, поэтому не повторяйте примеры, не задумываясь об этом. И, наконец, обратите внимание на то, что события от объектов WinJS не нуждаются в подобном внимании, так как библиотека уже обрабатывает удаление прослушивателей событий.

В следующих лекциях я напомню вам о том, что мы только что обсудили на нашей первой значимой встрече с событиями WinRT. В любом случае, будьте внимательны к цветовому выделению текста.

Завершение истории promise-объектов

Ух ты! Мы прошли большой путь в этой лекции, через множество тонких деталей того, как строятся приложения, и как они выполняются (или не выполняются!). Вы могли заметить, что наша постоянная тема касалась promise-объектов - они появлялись почти в каждом разделе. На самом деле, WinJS и WinRT изобилуют асинхронными операциями и выполняются они с помощью получения отложенных результатов, promise-объектов.

Я хочу завершить эту лекцию, однако, завершив историю promise-объектов, так как они обеспечивают гораздо более обширную функциональность, чем мы использовали. Демонстрацию того, что мы рассмотрим здесь, можно найти в примере "WinJS Promise" (http://code.msdn.microsoft.com/windowsapps/Promise-e1571015), а если вам нужна самая полная история асинхронных операций, прочтите материал "Использование асинхронности в среде выполнения Windows для создания быстрых и гибких приложений" (http://blogs.msdn.com/b/windowsappdev_ru/archive/2012/03/28/windows.aspx) в блоге разработчиков Windows 8.

Давайте сделаем шаг назад и уточним, что, на самом деле означает "promise". Говоря просто, это объект, который возвращает значение, простое или сложное, когда-то в будущем. Способ, благодаря которому вы узнаёте, когда доступно это значение - это вызов методов promise-объекта then или done с обработчиком завершения (completed handler). Этот обработчик будет вызван со значением, предоставленным promise-объектом (с обещанным значением) (с результатом (result)), когда это значение будет готово - это происходит немедленно, если значение уже доступно. Более того, вы можете вызывать then/done множество раз для одного и того же promise-объекта и вы просто получаете тот же самый результат в каждом обработчике завершения. Это не приведет к системному сбою или к чему-то подобному.

Если происходит ошибка, второй параметр у then/done это - обработчик ошибки (error handler), который будет вызван вместо обработчика завершения. В противном случае исключение будет либо поглощено в then, либо передано в цикл событий приложений done, как мы уже говорили об этом.

Третий параметр у then/done - это обработчик прогресса (progress handler), который периодически вызывается асинхронной операцией, поддерживающей его11Если вы хотите впечатлить своих друзей, когда читаете документацию, знайте, что если асинхронная функция показана возвращающей значение типа IAsync[Action | Operation]WithProgress, значит она может использовать функцию прогресса, переданную promise-объекту. Если же в её описании присутствует лишь IAsync[Action | Operation], то прогресс не поддерживается. Больше об этом вы можете найти в лекции 16.. Мы уже видели, например, как операции WinJS.xhr периодически вызывают функцию прогресса для отображения изменения "состояния готовности" при загрузке данных, запрошенных с сервера.

Сейчас нет требований к тому, чтобы promise-объект инкапсулировал асинхронные операции или синхронные. Вы можете, фактически, "обернуть" в этот объект любое значение, воспользовавшись статическим методом WinJS.Promise.wrap. Подобный контейнер для уже существующего значения (будущее - это сейчас!) будет ждать своего часа и вызовет обработчик завершения с данным значением, как только вы вызовете then или done. Это позволяет вам использовать любое значение там, где ожидается promise-объект, или возвращать что-то вроде ошибок из функций, которые в противном случае возвращают promise-объекты для асинхронных операций. WinJS.Promise.wraperror существует именно для этой специфической цели.

WinJS.Promise (http://msdn.microsoft.com/library/windows/apps/br211867.aspx), кроме того, поддерживает набор полезных статических методов, вызываемых напрямую из WinJS.Promise вместо того, чтобы пользоваться каким-то конкретным экземпляром promise-объекта.

  • is определяет, является ли произвольное значение promise-объектом, она обычно проверяет, обладает ли объект функцией с именем "then"; она не проверяет объекты на "done"
  • as работает схожим с wrap образом, за исключением того, что если вы предоставите ей promise-объект, она просто вернет этот объект. Если вы предоставите promise-объект функции wrap, она инкапсулирует его в еще один promise-объект.
  • any похожа на join, но группирует результаты с использованием логического ИЛИ (OR) (снова используя then)
  • cancel останавливает асинхронную операцию. Если предоставлен обработчик ошибок, он вызывается со значением Error("canceled").
  • theneach применяет обработчики завершения, ошибки, прогресса к группе promise-объектов (используя then), возвращая результаты в виде другой группы значений внутри promise-объекта.
  • timeout имеет двойственную природу. Если вы просто зададите значение тайм-аута, он вернет promise-объект, инкапсулирующий вызов setTimeout. Если вы, кроме того, предоставите promise-объект в качестве второго параметра, он отменит выполнение операции этого promise-объекта, если она не будет получена в заданное время. В последнем случае это обычная оболочка для стандартных шаблонов добавления тайм-аута для некоторых других асинхронных операций, у которых тайм-аута нет.
  • addEventListener/removeEventListenerdispatchEvent) управляет обработчиками события error, которое promise-объекты вызывают при возникновении исключения (но не в ответ на отмену). Прослушивание данного события не воздействует на использование обработчиков ошибок. Это дополнение, а не замена12Асинхронные операции из WinRT, упакованные в promise-объекты не вызывают это событие, говорящее об ошибке, вот поэтому вместо них обычно используют обработчик ошибок..

В дополнение к использованию функций наподобие as и wrap, вы так же можете создать promise-объект из заготовки, используя команду new WinJS.Promise(<init> [, <oncancel>). Здесь <init> - это функция, которая принимает диспетчеры (dispatcher) завершения, ошибки и прогресса, а oncancel - это необязательная функция, которая вызывается в ответ на WinJS.Promise.Cancel. Диспетчеры - это то, что вы вызываете, чтобы вызвать любые обработчики завершения, ошибки или прогресса, заданные методам promise-объекта then или done, в то время как oncancel - это ваша собственная функция, которую promise-объект вызовет, если он подвергнется операции отмены. Создание новых promise-объектов подобным способом обычно используют, когда создают собственные асинхронные функции. Например, мы увидим, как это используется для упаковывания асинхронного рабочего веб-процесса (web worker) в "Коллекции и элементы управления для вывода коллекций" курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой". Кроме того, если возможностей WinJS.Promise.as недостаточно, создание promise-объектов подобным образом полезно для упаковывания других операций (не только значений) в структуру promise-объекта, в итоге, такая операция может быть объединена в цепочку или соединена с другими операциями. Например, если у вас есть библиотека, которая обменивается данными с веб-сервисом посредством обычного асинхронного XmlHttpRequest, вы можете упаковать каждое API этой библиотеки в Promise-объект. Вы можете, кроме того, использовать новый promise-объект для того, чтобы упаковать множество асинхронных операций (или других promise-объектов!) из различных источников в единый promise-объект, в то время, как join или any не дадут вам нужного уровня контроля. Другой пример - инкапсуляция специфических функций обработки завершения, ошибок, прогресса операции в promise-объект, как при реализации механизма множественных вызовов поверх отдельных XHR-операций, для перехвата доступа к стандартному интерфейсу индикатора прогресса, или для добавления фонового ведения журнала, или подсистемы аналитики с вызовами сервиса, в итоге вашему коду никогда не понадобится знать об этих механизмах.

Что мы только что изучили:

  • Как локальный и веб-контексты влияют на структуру приложения, в плане воздействия на страницы, навигацию между страницами и элементы iframe.
  • Как использовать правила URI для содержимого для расширения доступа к ресурсам веб-контекста в iframe.
  • Использование схемы URI ms-appdata для адресации мультимедийного содержимого из локальной, перемещаемой, временной папок приложения.
  • Как исполнять серию асинхронных операций с использованием promise-объектов, объединенных в цепочку.
  • Как в promise-объектах, объединенных в цепочку, обрабатываются исключения и какова разница между then и done.
  • Методы для вывода отладочных данных и отчётов об ошибках от приложений, внутри отладчика и Средства просмотра событий Windows.
  • Как активируются приложения (начинают работу в памяти) и события, которые при этом происходят.
  • Структуру кода активации приложения, в том числе виды активации, состояния, предшествующие активации и объект WinJS.UI.Application.
  • Использование расширенных экранов-заставок, когда приложению нужно больше времени для запуска, и применение задержанных операций, когда приложению нужно использовать асинхронные операции при запуске.
  • Важные события, которые происходят в течение жизненного цикла приложения, такие, как события получения фокуса, изменения видимости, изменения режима просмотра и приостановка, возобновление и завершение работы приложения.
  • Основы сохранения и восстановления состояния сеанса работы приложения для возможности повторного старта приложения после завершения его работы, и утилиты WinJS для реализации этого.
  • Использование данных с сервисов посредством WinJS.xhr и как это связано с событием resuming.
  • Как реализовать навигацию между страницами в контексте единственной страницы с использованием элементов управления страниц, WinJS.Navigation и PageControlNavigator из шаблонов Visual Studio / Blender, таких, как шаблон Приложение навигации.
  • Все подробности о promise-объектах, которые обычно используются с асинхронными операциями, но не ограничены ими.
Анатолий Гречман
Анатолий Гречман
Казахстан, Экибастуз, Экибастузский Инженерно-технический Институт, 2014
Berkut Molodoy
Berkut Molodoy
Россия