Техническое примечание TN2277

Сети и многозадачность

Многозадачность была главной особенностью iOS 4. Многозадачность позволяет Вашему приложению быть помещенным в фон и, оттуда, приостановленным. Здорово для системной скорости отклика, но может серьезно влиять на возможность Вашего приложения работать с сетью. Этот technote объясняет, как лучше всего обработать многозадачность в сетевом приложении.

Необходимо считать этот technote при разработке приложения для iOS, использующего сеть.

Введение
Основы
Подробные данные реализации
Вне основ
Многозадачные супердержавы
История версии документа

Введение

Многозадачность, начатая с iOS 4, добавляет новый уровень сложности для сетевых приложений. Когда iOS помещает Ваше приложение в фон, оно может вскоре после того приостановить приложение. Когда приложение приостановлено, никакой код в процессе приложения не выполняется. Это лишает возможности приложение обрабатывать входящие сетевые данные. Кроме того, в то время как приложение приостановлено, система может принять решение предъявить претензии в отношении ресурсов из нижней части сетевой сокет, используемый приложением, таким образом закрыв сетевое соединение, представленное тем сокетом.

В то время как iOS 4 был тщательно создан для минимизации объема работы, должен был сделать сетевое приложение совместимым с многозадачностью, все еще необходимо выполнить некоторую работу для поддержания основной функциональности в приложении. Кроме того, выходом за пределы основ можно значительно улучшить сетевое поведение приложения в присутствии многозадачности.

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

Прежде, чем идти дальше, необходимо ознакомить себя с жизненным циклом приложения для iOS, как описано в Руководстве по программированию приложения для iOS. В частности этот technote использует передний план условий, фон и приостановленный в техническом смысле, определенном в том документе.

Наконец, объем этого technote нацелен на разработчиков, создающих приложения, не имеющие никаких специальных многозадачных возможностей. Однако, если Ваше приложение вписывается в одну из многозадачных категорий, описанных в Руководстве по программированию приложения для iOS, необходимо удостовериться, что считали информации в Многозадачных Супердержавах.

Основы

Весь iOS, объединяющий APIS В СЕТЬ, в конечном счете реализован с точки зрения Сокетов BSD API, в целях этого обсуждения, поддерживающий два основных типа сокетов:

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

Сокет слушания

Обработка сокета слушания в многозадачной среде очень проста: Ваше приложение должно закрыть сокет слушания, когда это входит в фон, и вновь откройте его, когда это возвращается в передний план. Существует две важных причины этого:

  • Как только Ваше приложение входит в фон, оно может быть приостановлено. Как только это приостановлено, это неспособно должным образом обработать входящие соединения на сокете слушания. Однако сокет все еще активен, насколько затронуто ядро. Если клиент соединится с сокетом, то ядро примет соединение, но Ваше приложение не свяжется по нему. В конечном счете клиент сдастся, но это могло бы требовать времени.

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

  • Если система приостанавливает Ваше приложение и затем, позже, предъявляет претензии в отношении ресурсов из-под Вашего слушающего сокета, Ваше приложение больше не будет прислушиваться к соединениям, даже после того, как это было возобновлено. Приложение может или не может быть уведомлено относительно этого, в зависимости от того, как оно управляет сокетом слушания. Когда приложение в фоновом режиме, обычно проще избежать этой проблемы полностью путем закрытия сокета слушания.

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

Сокет данных

Ситуация с сокетами данных является более тонкой, и лучший подход зависит от того, что делает Ваш сокет данных. Решение, которое необходимо принять, состоит в том, закрыть ли сокет данных, когда приложение входит в фон, или оставить ли сокет данных открытым, в то время как приложение в фоновом режиме и, что еще более важно, в то время как приостановлено приложение. Ключевой фактор в этом решении - то, как простой это должно закрыть и вновь открыть Ваш сокет данных:

  • Если повторное открытие Вашего сокета данных является простым, дешевым, и не имеет никакого пользователя видимые последствия, лучший подход должен закрыть сокет, когда приложение входит в фон, и вновь откройте его, когда это возвращается в передний план. Это делает Вашу жизнь очень простой; фактически, если Вы делаете это, можно проигнорировать остальную часть этого раздела полностью!

  • Если повторное открытие Вашего сокета данных является медленным, дорогим, или что-то, что мог бы заметить пользователь, Вы, вероятно, хотите сохранить сокет открытым при входе в фон. Это должно составить общий падеж, где пользователь переключается от Вашего приложения до некоторого другого приложения, и затем быстро переключается назад снова. Вы не хотите подвергать пользователя ненужным сетевым задержкам в этом случае. Однако при отъезде сокета данных открытым необходимо иметь дело с последствиями того решения, как описано в остальной части этого раздела.

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

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

Если протокол, который Вы говорите по сокету данных, поддерживает статический режим, необходимо включить его при входе в фон и отключить его при движении назад к переднему плану. Например, IMAP поддерживает команду IDLE, позволяющую клиентам быть уведомленными асинхронно, когда изменяется почтовый ящик. Нет, никакой смысл имеющий Ваше приложение не получает эти уведомления, в то время как оно приостановлено, таким образом, Вы определенно хотите отменить эти запросы IDLE при входе в фон.

Высокоуровневый APIs

Предыдущий раздел был записан с точки зрения сокетов, но уведомление применяется одинаково хорошо к высокоуровневым конструкциям, разделенным на уровни поверх сокетов. Например:

  • При использовании NSStream для управления соединением TCP, что NSStream бесплатный соединенный мостом к CFStream (фактически определенная разновидность CFStream, известного как CFSocketStream), который управляет базовым сокетом.

  • При использовании NSURLConnection он реализован с помощью CFStream (CFHTTPStream), который поочередно использует другой CFStream (CFSocketStream на сей раз), который управляет базовым сокетом.

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

  • При использовании NSStream Вы получите NSStreamEventHasBytesAvailable событие. Необходимо реагировать на это путем чтения из потока, в который точка -[NSInputStream -read:maxLength:] возвратится-1, указывая ошибку. Можно тогда получить фактическую ошибку путем вызова -[NSStream streamError].

  • При использовании NSURLConnection соединение вызовет Ваш -connection:didFailWithError: метод делегата сигнализировать ошибку.

Подробные данные реализации

В этом разделе описываются некоторые важные вещи иметь в виду при планировании ответа приложения на многозадачные переходы.

Остерегайтесь собаки!

Как описано в предыдущем разделе, Ваше приложение может закончить тем, что выполнило важную работу, поскольку это переходит от переднего плана до фона, обычно в -applicationDidEnterBackground: метод делегата. Жизненно важно, чтобы эта работа была выполнена быстро. Если Ваше приложение тратит слишком долго в -applicationDidEnterBackground:, это будет уничтожено сторожевым таймером.

В этом контексте любая работа, включающая ожидание сети, является «слишком длинной». Поэтому сеть могла бы стать безразличной в точно точке, которую Ваше приложение перемещает к фону. Если это произойдет, то приложение закончит тем, что тратило слишком долго внутри -applicationDidEnterBackground:, и закончится как корм для собак.

Не все сетевые операции включают ожидание. Например, это прекрасно подходит для окружения сокета слушания Ваш -applicationDidEnterBackground: метод делегата; закрытие сокета слушания всегда быстро. Даже возможно передать данные в этой ситуации. Например, если Вы хотите отправить команде помещение соединения в статический режим, это в порядке, пока существует пространство для команды в буфере сокета ядра (т.е. сокет не находится в управлении потоком стороны записи). В этом случае отправка команды просто копирует данные в буфер сокета, который не блокирует ни для какого существенного количества времени.

То, где Ваше приложение может попасть в неприятное положение, - когда это пытается отправить команду и ожидать ответа. Если сеть будет безразлична, то ответ будет задержан, и Ваше приложение будет уничтожено сторожевым таймером.

Если необходимо ожидать сети в этой ситуации, необходимо использовать фоновую задачу (создаваемый через -[UIApplication beginBackgroundTaskWithExpirationHandler:]) управлять тем процессом. Например, скажем, к статическому режиму Вашего протокола переходят посредством заморозить команды, ответа которой необходимо ожидать. В этом случае Ваш -applicationDidEnterBackground: метод делегата должен:

  1. запустите асинхронную работу, чтобы отправить заморозить команду и ожидать ответа

  2. начните фоновую задачу, чтобы запросить дополнительное время на ту работу завершиться

  3. возвратитесь из -applicationDidEnterBackground:

Это даст Ваше дополнительное время приложения для выполнения заморозить команды, не тратя слишком долго в -applicationDidEnterBackground:.

Следует иметь в виду, что все фоновые задачи должны предоставить обработчик истечения срока, который системные вызовы, когда он требует, чтобы фоновая задача сразу завершилась. Обработчик истечения срока, как -applicationDidEnterBackground: метод делегата, контролируется сторожевым таймером; если обработчик истечения срока будет брать слишком долго, то приложение будет уничтожено. Таким образом только безопасно сделать сетевую операцию от Вашего обработчика истечения срока, если можно гарантировать, что это не будет ожидать сети. Это - точка, где Вы могли бы хотеть в одностороннем порядке закрыть сокет данных, не пытаясь сказать до свидания удаленному узлу.

Параллелизм

Если Вы планируете сделать какие-либо полезные сети в приложении для iOS, вероятно, что Вы закончите тем, что использовали фоновые задачи для продолжения работы, даже если только временно, как переходы приложения к фону. При запуске фоновой задачи, необходимо предоставить обработчик истечения срока, который может отменить фоновую задачу. Обработчик истечения срока работает на основном потоке, и это должно отменить фоновую задачу, прежде чем это возвратится (потому что Ваше приложение будет приостановлено или завершено вскоре после). И, как упомянуто в предыдущем разделе, обработчик истечения срока должен выполниться быстро, потому что, если это берет слишком долго, Ваше приложение будет уничтожено сторожевым таймером.

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

Например, рассмотрите случай, где сетевой поток слушает, синхронным способом блокирования, для входящего соединения. В этом случае поток обычно блокировался бы в принять системном вызове. Когда пользователь нажимает кнопку «Домой», Ваше приложение помещается в фоновом режиме, и Вы хотите, чтобы он закрыл свой слушающий сокет. Как делают Вы разблокируете поток, это ожидает внутри accept? Короткий ответ - то, что нет никакого хорошего способа сделать это.

Также рассмотрите случай, где Ваше приложение делает большую загрузку в фоновом режиме, и сеть является действительно медленной, столь медленной фактически что выполнение приложения из времени фоновой задачи. В этой точке система выполняет обработчик истечения срока фоновой задачи на основном потоке, и приложение должно закрыть соединение перед возвратом. Как дела это, когда там поток блокируется в чтении, ожидающем данных от сети? Снова, ответ - то, что нет никакого хорошего способа сделать это.

Результат этого - то, что, для поддержки многозадачности хорошо, Вы, вероятно, захотите использовать асинхронную сеть APIs. iOS обеспечивает большое разнообразие APIs, чтобы сделать это — от низкоуровневого APIs как GCD к высокоуровневому APIs как NSURLConnection со многими промежуточными остановками — и мы призываем Вас использовать их.

Выполненные циклы

При использовании цикла выполнения, базируемого сетевой API можно счесть необходимым выполнить цикл выполнения из обработчика истечения срока для предоставления базируемой инфраструктуры цикла выполнения, которую должно было закончить время. Если Вы делаете это, необходимо выполнить цикл выполнения в пользовательском режиме цикла выполнения; выполнение цикла выполнения в выполненном режиме цикла по умолчанию в обработчике истечения срока (или, в этом отношении, в -applicationDidEnterBackground:) не что-то, что мы поддерживаем. Следует иметь в виду, что при использовании этого метода необходимо запланировать все соответствующие источники цикла выполнения в пользовательском режиме цикла выполнения, а также в режиме по умолчанию.

Тестирование предъявления претензий сокета

Если Вы собираетесь записать код, обрабатывающий ресурсы сокета, предъявляемые претензии ядром, необходимо выяснить, как протестировать его. Точные обстоятельства, при которых система могла бы предъявить претензии в отношении ресурсов сокета, намеренно не документируются; это дает нам гибкость для улучшения вещей в будущем. Однако на существующих системах (iOS 4.0 через iOS 4.3), можно заставить систему предъявлять претензии в отношении ресурсов от сокетов в приложении:

  1. помещение приложения в фоновом режиме

  2. обеспечение, что приостановлено приложение

  3. блокировка экрана

Когда Ваше приложение возобновит выполнение, это найдет, что это - сокеты, были предъявлены претензии.

Вне основ

Если Ваше приложение делает продолжительные сетевые передачи, существует две вещи, которые можно сделать для улучшения пользовательского опыта:

Фоновые задачи

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

Когда реализация фоновой задачи поддерживает в Вашем приложении, не необходимо для Вас иметь отдельную логику для «в фоновом режиме» и «в приоритетных» случаях. Это прекрасно подходит Вашему приложению для начала фоновой задачи каждый раз, когда это запускает продолжительную работу, даже если приложение находится на переднем плане. В то время как приложение находится на переднем плане, фоновая задача не будет иметь никакого эффекта; однако, если пользователь переместит приложение в фон, то существование фоновой задачи автоматически сохранит выполнение приложения так, чтобы это могло завершить работу.

Передачи Resumable

Даже при использовании фоновой задачи для продолжения долгих передач, все еще важно что поддержка приложений resumable передачи. Существуют многочисленные ситуации, где это могло бы быть полезно:

  • если это работает на устройстве, не поддерживающем многозадачность

  • если сетевое соединение прервано во время передачи

  • если Ваше приложение просит запускать фоновую задачу, и система отказывается делать так (т.е. если -[UIApplication beginBackgroundTaskWithExpirationHandler:] возвраты UIBackgroundTaskInvalid)

  • если системные ресурсы понижаются, и таким образом система должна приостановить или завершить Ваше приложение, прежде чем его фоновые задачи будут завершены

  • если передача занимает больше времени, чем фоновая задача позволит

Все эти ситуации получат преимущества от Вас реализующий resumable передачи.

Реализация resumable загрузок обычно проста. Если Вы используете HTTP, большинство тегов вспомогательного объекта серверов и диапазонов байта, фундаментальные стандартные блоки resumable загрузок. Кроме того, это просто в использовании эти функции от NSURLConnection. Необходимо считать спецификацию протокола HTTP (RFC 2616) для узнавания больше об этих средствах.

Resumable, который загружает HTTP, немного более хитер, и это также зависит от сервера, который Вы загружаете также. Может не быть возможно реализовать resumable загрузки, не изменяя Ваш сервер для размещения их.

Реализация resumable загрузок FTP также проста; при возобновлении загрузки можно установить kCFStreamPropertyFTPFileTransferOffset свойство для инструктирования CFFTPStream, где начать загружать с.

CFFTPStream не поддерживает resumable загрузки FTP (r. 4086998). С другой стороны, данный FTP's плохие средства защиты (имя пользователя, пароль и данные все передаются как простой текст) трудно вообразить любое использование загрузок FTP, подходящее для современного Интернета.

Многозадачные супердержавы

Некоторые приложения имеют специальные полномочия при работе многозадачной системы. Например, приложение аудиоплеера может играть музыку в фоновом режиме без того, чтобы когда-нибудь быть приостановленным. Этот раздел объясняет отношение между сетями и этими многозадачными 'супердержавами'.

Существует два типа специальных полномочий, касающихся сетей:

Они обсуждены в следующих разделах.

Фоновое выполнение

Наиболее распространенное многозадачное специальное питание является возможностью выполнить код в фоновом режиме. Например, когда приложение аудиоплеера играет музыку, оно не будет приостановлено после перемещения в фон. Объем этого документа использовал термин, «перемещается в фон», поскольку сокращение для «становится имеющим право на приостановку». Это - хорошее эмпирическое правило, но оно разбивается при контакте с приложениями, которые могут выполниться в фоновом режиме. В этих приложениях действие перемещения в фон автоматически не делает приложение имеющим право на приостановку. Скорее приложение становится имеющим право на приостановку, когда это прекращает делать вещь, препятствовавшую тому, чтобы он был, приостанавливают во-первых. Для продолжения примера аудиоплеера приложение становится имеющим право на приостановку, когда это прекращает играть музыку.

Обратите внимание на то, что фоновые задачи (как обсуждено в Остерегаются Собаки! и Вне Основ), особый случай этого питания. Любое приложение может выполнить код в фоновом режиме для ограниченного количества времени путем начала фоновой задачи. Когда ее последняя фоновая задача заканчивается, такое приложение становится имеющим право на приостановку.

Сокеты VoIP

VoIP (Речь по IP), которую приложение, как ожидают, будет выполнять постоянно так, чтобы это могло контролировать соединение управления VoIP; однако, для минимизации влияния памяти на систему приложение приостановлено, в то время как это неактивно. Для создания этой работы, приложение должно зарегистрировать сокет данных для своего соединения управления как сокет VoIP. Сокет, зарегистрированный таким образом, имеет две специальных функции:

  • В то время как приложение приостановлено системные мониторы сокет от имени приложения. Если какие-либо данные поступают в сокет, системное выполнение резюме приложения (хотя в фоновом режиме), который может тогда считать данные от сокета и принять соответствующие меры (например, уведомив пользователя входящего вызова).

  • В отношении ресурсов сокета никогда не предъявляют претензии. Таким образом приложение может безопасно быть приостановлено без любого страха перед его сокетом, идущим глухой.

Для получения дополнительной информации о том, как создать приложение VoIP, см. Руководство по программированию приложения для iOS.



История версии документа


ДатаПримечания
30.03.2011

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