В современном мире наличие публичного Wi-Fi в общественных заведениях считается само собой разумеющимся. Посещая кафе, торговые центры, отели, аэропорты, парки отдыха и многие другие места, мы сразу ищем заветный сигнал без пароля. А это бывает нелегко, поскольку, во-первых, точек в списке может оказаться несколько, а во-вторых, бесплатный Wi-Fi может быть запаролен, так что единственный выход — ловить сотрудника, который сможет указать на правильную сеть и назвать пароль. Но даже после этого случается так, что ничего не работает. Пользователь должен догадаться о том, что ему необходимо открыть браузер (причем еще вопрос, какую страницу следует загружать) и совершить дополнительные действия (авторизоваться, просмотреть рекламу, подтвердить пользовательское соглашение), прежде чем ему предоставят полноценный доступ в сеть.
Впрочем, сейчас многие популярные заведения предлагают приложения, облегчающие подключение к бесплатным точкам. Уверен, что каждый из нас сможет легко припомнить пару-тройку подобных примеров, поэтому обойдусь без названий и рекламы. Тем более что ниже пойдет речь о другом варианте решения этой проблемы — мы будем писать собственный Network Helper! С таким подходом больше не придется гадать, к какой сетке подключаться. Даже дополнительные действия для получения доступа в сеть можно будет производить в удобном нативном UI и гораздо быстрее, чем в браузере.
Все просто. Достаточно задействовать технологию NEHotspotHelper, которая стала доступна разработчикам еще со времен выхода iOS 9. Основная задача этого инструмента — классификация Wi-Fi-сетей и авторизация пользователя в них. NEHotspotHelper входит в состав фреймворка NetworkExtension. Чуть ниже вы найдете схему входящих в него на момент выхода iOS 11 инструментов:
Основная документация находится тут: Hotspot Network Subsystem Programming Guide. Помимо этого никакой информации в сети обнаружить не удалось, в связи с чем я и пишу эту статью. Надеюсь, мой материал поможет восполнить пробелы документации и объяснит, как реализовать свой собственный Hotspot Helper. Пример использования этой технологии можно найти на GitHub.
Принцип работы
В основе работы Hotspot Helper — машина состояний Wi-Fi-соединения и посылаемые системой Helper команды, обработка которых переводит машину из одного состояния в другое. Ниже представлена приблизительная схема состояний, которую приводит сама Apple в своей документации:
Поначалу столь сложная картинка пугает, однако не стоит беспокоиться — на практике все довольно просто.
Достаточно понимать следующее:
- При первом подключении к сети после перезагрузки устройства выбирается Helper, который будет её обслуживать (Evaluate).
- Как только выбор сделан, запускается авторизация в сети с помощью выбранного Hotspot Helper (Authenticating).
- Если в процессе авторизации необходимо отобразить UI, Hotspot Helper его явно запрашивает (PresentingUI). Если такой нужды нет, Helper в фоне совершает необходимые действия по авторизации пользователя в сети (Authenticated).
- Периодически система пробуждает выбранный Hotspot Helper для поддержки сессии, если это необходимо (Maintain).
- Во время поддержки сессии Helper может ничего не делать, а может запросить повторную авторизацию или спровоцировать повторное подключение к сети.
Единственный неочевидный момент: после первого подключения к сети система кеширует выбранный для нее Hotspot Helper, поэтому при последующем подключении машина cразу переключается в состояние Maintain, минуя все предыдущие.
Hotspot Helper участвует в авторизации пользователя в Wi-Fi-сети на всех этапах — от отображения списка сетей и подключения к выбранной до поддержки авторизации и самостоятельного логаута. При этом для установки соединения Hotspot Helper обрабатывает все требуемые команды, благодаря чему система обеспечивает его запуск в любой ситуации (даже если пользователь принудительно выключит приложение (что привело бы к игнорированию silent-push уведомлений). Ведь от этого зависит работа всего устройства. Надо понимать, что для пользователя весь процесс протекает прозрачно. Таким образом, наиболее частый сценарий — это запуск приложения в фоне.
Итак, повторим: для установки Wi-Fi-соединения Hotspot Helper должен обработать все требуемые команды. Иными словами, устройство не будет считать себя подключенным к сети до тех пор, пока StateMachine не перейдет в состояние Authenticated. И это несмотря на то, что Hotspot Helper начнет видеть соединение уже при получении команды Evaluate. Этот момент отлично отслеживается средствами Reachability, о чем мы поговорим чуть ниже.
Надо сказать, что NEHotspotHelper — не отдельный target, как это часто бывает с другими extension, а основное приложение, зарегистрированное как Hotspot Helper. Это значит, что, как только ему потребуется обработать какую-либо команду, будет запущено основное приложение со всеми вытекающими последствиями. То есть у приложения будет возможность выполнять любой код, однако при запуске в фоне оно может развернуть полномасштабные действия, как будто инициированные самим пользователем. Однако такая деятельность будет означать лишь расход ресурсов впустую, поэтому за происходящим в фоне стоит следить.
Предварительная подготовка
Для регистрации приложения как Hotspot Helper нужно получить разрешение у Apple. Для этого Team Agent должен перейти по ссылке и заполнить опросник.
На момент написания статьи он выглядит так:
Если все пройдет хорошо, то при создании provisioning profile на https://developer.apple.com появится возможность выбрать для него entitlement с ключом com.apple.developer.networking.HotspotHelper, дающий право на использование всех плюшек.
Кроме того, необходимо включить в проекте Background Mode Capability и прописать в Info.plist в раздел UIBackgroundModes строку network-authentication. После этого можно приступать к самому интересному — кодингу.
Регистрация
Для того чтобы приложение стало Hotspot Helper, его необходимо зарегистрировать в системе. Для этого надо вызвать следующий метод:
class NEHotspotHelper
class func register(options: [String : NSObject]? = nil, queue: DispatchQueue,
handler: @escaping NetworkExtension.NEHotspotHelperHandler) -> Bool
Метод принимает три параметра:
- Опциональный словарь параметров. Сейчас поддерживается единственный параметр: kNEHotspotHelperOptionDisplayName — имя Hotspot Helper, которое будет отображаться в списке доступных Wi-Fi-сетей рядом с теми сетями, которые HH поддерживает (об этом ниже). Сменить это имя можно только после перезапуска приложения, передав новое имя в качестве параметра. Согласно требованиям Apple, имя должно быть максимально коротким.
- Очередь, на которой будут обрабатываться команды, получаемые от системы. Её можно использовать не только для обработки команд в фоне, но и для синхронизации обработки команд с другим кодом приложения.
- Блок — обработчик команд. Это ключевой элемент конструкции. Его сигнатура проста:
typealias NEHotspotHelperHandler = (NEHotspotHelperCommand) -> Void
Он принимает команду системы в качестве единственного параметра и не возвращает ничего. Блок будет вызываться на очереди, переданной в качестве второго параметра.
Регистрировать Helper надо ровно один раз на каждом запуске. Повторный вызов в том же запуске не дает ничего и возвращает false. Важно отметить, что до завершения повторной регистрации приложение не получит команды, для обработки которой оно было запущено системой.
Отменить регистрацию никак нельзя. Можно только перестать регистрировать блок, тогда приложение никак не будет обрабатывать соединение, но запускатьcя все равно будет — подробнее тут.
Кроме того, в отличие от многих других функций системы (таких как фотогалерея, календарь и даже уведомления), обработка Wi-Fi-соединения средствами Hotspot Helper не требует никаких разрешений от пользователя и происходит прозрачно для него (он попросту не сталкивается с подобными понятиями).
Обработка команд
Команда представляет собой объект класса NEHotspotHelperCommand, содержащий тип и набор данных, характерный для каждой из команд (сеть либо список сетей, если это подразумевает команда).
После обработки каждой из команд следует создать NEHotspotHelperResponse с результатом выполнения и набором данных, который также зависит от конкретной команды.
Объект NEHotspotHelperResponse создается вызовом на полученной команде данного метода:
func createResponse(_ result: NEHotspotHelperResult) -> NEHotspotHelperResponse
Кроме того, использование объекта команды позволяет установить TCP- либо UDP-соединение на основании сети, к которой команда относится, вызывая соответствующие методы:
func createTCPConnection(_ endpoint: NWEndpoint) -> NWTCPConnection
func createUDPSession(_ endpoint: NWEndpoint) -> NWUDPSession
Для более высокоуровневого общения с сервером можно создать NSURLRequest. Приложив к нему команду, Hotspot Helper получает возможность взаимодействия с сервером в условиях, когда устройство не видит Wi-Fi-соединения. Установленное таким образом соединение можно использовать для авторизации «по-своему». IYKWIM
func bind(to command: NEHotspotHelperCommand)
Ниже рассмотрим каждую команду, которую может получить Hotspot Helper, в порядке, соответствующем базовому сценарию подключения к сети. Большинство команд названы аналогично состояниям State Machine, при которых они вызываются.
Официально на выполнение каждой команды отводится не более 45 секунд (однако, если посмотреть на доступное время работы в фоне, можно увидеть цифру 60 секунд). После этого команда считается необработанной, а работа Hotspot Helper приостанавливается. Это ограничение необходимо, чтобы устранить излишние задержки при подключении к сети, ведь пока команда не будет обработана, пользователь не увидит заветного значка Wi-Fi в Status Bar. При этом надо понимать, что, если в системе несколько Hotspot Helper, которые обрабатывают одну и ту же сеть, будет выбран самый быстрый из них (об этом опять же ниже).
NEHotspotHelperCommandType.filterScanList
Это особенная команда, которая, в отличие от остальных, не привязана ни к одному из состояний StateMachine и может быть вызвана в любой момент. Команда вызывается на всех Hotspot Helper, известных системе, каждые 5 секунд. Происходит это все время, пока пользователь находится на экране списка Wi-Fi-сетей в системном Settings.app.
Команда служит единственной цели — продемонстрировать пользователю, какие из сетей обрабатывает Hotspot Helper. Для этого команда содержит список доступных сетей в соответствующем поле:
var networkList: [NEHotspotNetwork]? { get }
Это тот же список, который увидит пользователь. При этом список сетей может меняться с каждым новым вызовом команды.
Предполагается, что Hotspot Helper должен проанализировать список сетей и в ответе на команду вернуть те из них, которые он готов обслуживать. Пустой массив в ответе будет означать, что сетей доступных для обслуживания этим Helper в списке нет. Скрыть для пользователя сети из списка не получится.
У тех сетей в списке, которые вернул Hotspot Helper в ответ на эту команду, появится подпись. Она будет соответствовать тому значению, которое было передано при последней регистрации Hotspot Helper в качестве опции kNEHotspotHelperOptionDisplayName. Значение подписи может быть только одно. Оно задается при регистрации и не может быть изменено до следующей регистрации (а это происходит после перезапуска приложения), так что подписывать сети по-разному, увы, не получится.
Важно отметить, что кроме передачи самой сети в ответ ей необходимо задать пароль, если она им защищена. Без этого подпись не появится. Если же пароль задан, то кроме появления подписи пользователю не потребуется вводить пароль самому. Надо сказать, это тот самый единственный момент, когда можно установить пароль для сети. Если не задать его сейчас, то это придется делать самому пользователю.
В итоге обрабатывать команду следует так:
let network = <A network from command.networkList>
network.setPassword("PASSWORD")
let response = command.createResponse(.success)
response.setNetworkList([network])
response.deliver()
В результате пользователь увидит нечто подобное:
Надо понимать, что команда filterScanList целиком и полностью служит для демонстрации пользователю списка сетей и никак не влияет на остальную обработку подключения к сети. Если в ответ на команду Hotspot Helper не вернул ни одну сеть, ему все равно предложат обработать подключение к любой из них посредством команд, описанных ниже.
Интересный факт: если удалить приложение, подписи в списке сетей сохранятся вплоть до перезапуска устройства.
NEHotspotHelperCommandType.evaluate
Данная команда вызывается при первом подключении к сети на всех Hotspot Helper, известных системе.
Note: при последующих подключениях evaluate не вызовется, сразу последует maintain на том Hotspot Helper, который был выбран в процессе evaluate.
Цель этой команды — первичное выявление наиболее подходящего для обработки подключения к выбранной сети Hotspot Helper. Для этого вместе с командой Hotspot Helper получает и данные по сети, к которой производится подключение:
var network: NEHotspotNetwork? { get }
Network содержит ряд свойств, однако в процессе обработки команды evaluate значения имеют только следующие:
// Идентифицируют сеть
var ssid: String { get }
var bssid: String { get }
// Отражают силу сигнала по шкале от 0.0 до 1.0 (дБ, увы, не предоставляются)
var signalStrength: Double { get }
// Признак необходимости ввода пароля для подключения к сети
var isSecure: Bool { get }
// Признак, показывающий, было ли подключение выполнено автоматически
// или пользователь явно выбрал эту сеть в настройках
var didAutoJoin: Bool { get }
Получив эту информацию, Hotspot Helper должен проанализировать сеть любым способом (начиная от локальной таблицы и заканчивая запросом на сервер) и выставить сети соответствующий уровень confidence:
// Helper уверен, что не обрабатывает эту сеть.
case none
// Helper предполагает, что сможет обработать подключение к этой сети, но не уверен полностью*.
case low
// Helper уверен, что может полноценно обработать подключение к этой сети.
case high
*В этом случае ему могут предоставить возможность авторизовать пользователя, однако если в процессе Helper поймет, что он несовместим с данной сетью, то он сможет отказаться от работы с ней, и StateMachine вновь перейдет в состояние evaluate, а Helper будет добавлен в список исключений по данной сети.
Важно отметить: Apple настоятельно дает понять, что уровень confidence надо выбирать тщательно и не следует бездумно выставлять всем сетям high (и даже low), так как это напрямую сказывается на UX всей системы.
В итоге обрабатывать команду следует так:
let network = command.network
// Оценить сеть и определить уровень confidence...
network.setConfidence(<Appropriate level of confidence>)
let response = command.createResponse(.success)
response.setNetwork(network)
response.deliver()
На обработку команды предоставляется 45 секунд, при этом есть смысл постараться сделать это как можно быстрее. Потому что, как только система получает первый ответ с high confidence, обработка команды прекращается. Затем ответивший Hotspot Helper выбирается для дальнейший обработки подключения в сети, а все остальные прекращают свою работу и переходят в suspended state.
NEHotspotHelperCommandType.authenticate
Команда authenticate вызывается на наиболее подходящем Hotspot Helper по результатам выполнения команды evaluate.
Целью этой команды служит выполнение всех действий, необходимых для предоставления полного доступа пользователя к выбранной Wi-Fi-сети. Для этого вместе с командой Hotspot Helper получает и данные по сети, к которой производится подключение:
var network: NEHotspotNetwork? { get }
В обработке этой команды и заключается основная суть работы Hotspot Helper. На этом этапе барьером между пользователем и доступом к сети является только Hotspot Helper, и он должен любым доступным фантазии разработчика способом решить, предоставлять пользователю доступ или нет.
На обработку команды дается 45 секунд, по истечении которых необходимо вернуть response с одним из следующих результатов:
.success — авторизация успешно завершена. Доступ в сеть полностью открыт. StateMachine переходит в состояние authenticated.
.unsupportedNetwork — данная сеть не поддерживается Helper: произведен неправильный анализ сети на этапе evaluate. StateMachine возвращается на этап evaluate, а Helper добавляется в исключения для данной сети. Такое может произойти, например, если Helper вернул для этой сети low confidence на этапе evaluate, а сейчас убедился, что не справится.
.uiRequired — требуется взаимодействие с пользователем. Этот результат возвращается в случае, если для авторизации требуется ввести какие-либо дополнительные данные. Однако не все так просто: UI не покажется сам собой при возвращении этого результата.
Работает это так: Hotspot Helper генерируется UILocalNotification, посредством которой сообщает пользователю о необходимости дополнительного взаимодействия. Если пользователь ее проигнорирует, ничего больше не произойдет. Получив результат обработки, StateMachine переходит в состояние presentingUI и остается в нем до завершения авторизации либо отключения от сети.
.temporaryFailure — исправимая ошибка. Например, в ходе авторизации произошла сетевая ошибка. StateMachine переходит в состояние failure, устройство отключается от выбранной сети.
.failure (или любой другой результат, а равно и его отсутствие) — неисправимая ошибка. Что-то пошло совсем не так, и обработать подключение нельзя: например, поменялся протокол авторизации на стороне сервера, а клиент к этому не готов. StateMachine переходит в состояние failure, устройство отключается от выбранной сети. Дополнительно, в отличие от temporaryFailure, для такой сети отключается функция auto-join.
В итоге обрабатывать команду следует так:
let network = command.network
// Авторизовать пользователя необходимым образом и сформировать результат обработки команды
// Вывести UILocalNotification в случае необходимости дополнительного взаимодействия (.uiRequired)
command.createResponse(<Command result>).deliver()
NEHotspotHelperCommandType.presentUI
Эта команда вызывается на выбранном Hotspot Helper в случае, если он вернул результат uiRequired в процессе обработки команды authenticate.
Это единственная команда, которая не пробуждает приложение в фоне и имеет неограниченное время на выполнение. Прилетает она только после того, как пользователь запустит приложение, например, получив UILocalNotification о необходимости дополнительного взаимодействия в процессе обработки команды authenticate.
Как раз на этом этапе следует просить пользователя ввести его доменные креды, если сеть корпоративная, либо показывать, например, рекламу, если сеть коммерческая. Словом, варианты ограничены только фантазией и здравым смыслом разработчика.
В итоге необходимо вернуть response с одним из следующих результатов:
.success — авторизация успешно завершена. Доступ в сеть полностью открыт. StateMachine переходит в состояние authenticated.
.unsupportedNetwork — данная сеть не поддерживается Helper: произведен неправильный анализ сети на этапе evaluate. StateMachine возвращается на этап evaluate, а Helper добавляется в исключения для данной сети. Такое может произойти, например, если Helper вернул для этой сети low confidence на этапе evaluate, а сейчас убедился, что не справится.
.temporaryFailure — исправимая ошибка. Скажем, в ходе авторизации произошла сетевая ошибка. StateMachine переходит в состояние failure, устройство отключается от выбранной сети.
.failure (или любой другой результат, а равно и его отсутствие) — неисправимая ошибка. Что-то пошло не так, и обработать подключение нельзя: например, поменялся протокол авторизации на стороне сервера, а клиент к этому не готов. StateMachine переходит в состояние failure, устройство отключается от выбранной сети. Дополнительно, в отличие от temporaryFailure, для такой сети отключается функция auto-join.
В итоге обрабатывать команду следует так:
let network = command.network
// Произвести любые необходимые действия для авторизации пользователя любыми средствами
// за неограниченные период времени и сформировать результат выполнения команды
command.createResponse(<Command result>).deliver()
NEHotspotHelperCommandType.maintain
Команда maintain, как можно догадаться по её названию, предназначена для поддержания сессии авторизации пользователя в текущей сети. Она вызывается на выбранном для сети Hotspot Helper в процессе evaluate в двух случаях:
- Каждые 300 секунд (пять минут) на протяжении всего времени подключения устройства к Wi-Fi-сети.
- При установке подключения к сети вместо команды evaluate, для обработки которой текущий Hotspot Helper был выбран ранее.
Предполагается, что в обоих случаях Hotspot Helper проанализирует текущее состояние сессии авторизации и выполнит одно из следующих действий:
- мгновенно предоставит (продолжит предоставлять) пользователю доступ в сеть, вызвав response с результатом .success;
- потребует повторной авторизации, вызвав response с результатом .authenticationRequired (в этом случае StateMachine переходит в состояние Authenticating и посылает Hotspot Helper команду authenticate).
- сообщит о невозможности продолжить сессию, завершив команду с кодом ошибки ,(.temporaryFailure/.failure или любым другим, отличным от перечисленных выше). В этом случае StateMachine переходит в состояние Evaluating для выбора того Hotspot Helper, который сможет обработать соединение.
Для отличия первого случая вызова команды maintain от второго в массиве данных по сети, передаваемых вместе с командой, предусмотрен специальный флаг — didJustJoin.
В итоге обрабатывать команду следует так:
let network = command.network
if network.didJustJoin {
// Новое подключение к сети, для обработки которой выбран данный Helper
}
else {
// Поддержка сессии в сети, в которой была произведена авторизация (раз в 300 сек.)
}
// Обеспечить авторизацию пользователя в сети любым способом
// и сформировать результат обработки команды
command.createResponse(<Command result>).deliver()
Следует отметить, что во время повторной авторизации соединение будет недоступно для устройства вплоть до возвращения в состояние Authenticated.
NEHotspotHelperCommandType.logoff
Команда logoff, как можно было бы ожидать, не посылается при отключении от сети. Отследить отключение от сети средствами Hotspot Helper невозможно.
Эта команда предназначена для завершения внутренней сессии авторизации выбранного Hotspot Helper и посылается ему в ответ на вызов статического метода NEHotspotHelper:
class func logoff(_ network: NEHotspotNetwork) -> Bool
Данный метод может быть успешно вызван только с текущей сетью в качестве параметра, только тем Hotspot Helper, который производил авторизацию в ней, и только когда приложение активно. В противном случае метод вернет false и команда не будет вызвана.
В результате StateMachine переходит в состояние LoggingOff, а Hotspot Helper получает заветную команду и 45 секунд на её выполнение.
Получить сеть, которую необходимо передать в метод, можно следующим образом:
let network = NEHotspotHelper.supportedNetworkInterfaces().first
Основной use-case: выполнение логаута из UI приложения, что актуально для сценария авторизации с использованием команды presentUI.
Как только Hotspot Helper завершит выполнение команды (либо время выполнения истечет), StateMachine переходит в состояние inactive, и устройство отсоединяется от Wi-Fi-сети.
В итоге обрабатывать команду следует так:
let network = command.network
// Произвести logoff и сбросить внутренние данные сессии авторизации
let response = command.createResponse(.success).deliver()
Следует отметить, что до получения этой команды приложение не должно пытаться выполнить какие-либо действия по отключению от сети, так как это негативно повлияет на UX всей системы (фактически пользователь будет видеть наличие соединения, но данные при этом ходить не будут).
Что еще полезно знать
Выше описаны принцип работы и детали реализации Hotspot Helper. Однако есть еще несколько особенностей, о которых следует знать, приступая к разработке, а именно:
- Hotspot Helper нельзя зарегистрировать на симуляторе — для разработки и отладки потребуется реальное устройство.
- Зарегистрированное как Hotspot Helper приложение будет запущено системой в фоне в любой ситуации, поскольку от этого зависит работа всей системы. Даже если приложение выгрузил пользователь, отключил background fetch, включил low power mode и т.д.
- Hotspot Helper не предоставляет никаких возможностей отслеживать отключения от сети (пусть команда logoff не вводит в заблуждение: это обработка завершения сессии авторизации самим Helper). Если необходимо мониторить отключение, следует воспользоваться уведомлениями от reachability (само собой, приложение при этом должно быть активно — в фоне система вас не поднимет).
- Сеть, к которой подключено устройство в данный момент, можно узнать следующим образом:
let network = NEHotspotHelper.supportedNetworkInterfaces().first
Следует отметить, что, несмотря на swift-сигнатуру данного метода (в которой указан non-optional-массив в качестве результата) и ожидаемое поведение (отсутствие сети представляется пустым массивом), при отсутствии соединения можно получить объект сети с пустыми строками в качестве SSID и BSSID и силой сигнала 0.0. Иногда, что еще страшнее, на выходе можно получить и nil (а вместе с ним и crash). В примере приведен код, позволяющий избежать этих ситуаций.
Note: C выходом iOS 11 эта проблема устранена.
- Подключение к сети для всего устройства появляется только после того, как StateMachine перейдет в состояние Authenticated. Это хорошо видно с использованием reachbility, который не будет видеть соединения до тех пор, пока Hotspot Helper не обработает все необходимые команды.
Следует отметить, что существует единственная ситуация, в которой reachability уже видит Wi-Fi, а Hotspot Helper не получает никаких команд. Происходит это, когда пользователь может видеть следующее состояние в настройках:
Чем обусловлена такая ситуация, понять сложно. Если у вас есть идеи, поделитесь ими, пожалуйста.
- Несколько Helper в системе.
Не существует никаких ограничений на количество Hotspot Helper, одновременно зарегистрированных в системе. Это значит, что возможна ситуация, при которой за обработку одной и той же сети будут конфликтовать несколько приложений.Конфликт решается на этапе обработки команды evaluate: выбирается тот Hotspot Helper, который вернул high-confidence для сети быстрее остальных. Вся дальнейшая обработка для этой сети происходит с использованием только этого Helper. Выбранный Helper может потом отказаться от обработки данной сети, вернув соответствующий код результата в процессе обработки очередной команды. Но если он этого не сделает, для остальных Hotspot Helper нет никакой возможности поучаствовать в обработке соединения.
Ситуация усугубляется еще и тем, что пользователь ничего не знает и не может знать о существовании каких-либо Helper. Все это происходит незаметно для него: нигде не указывается данный функционал, никакие разрешения у него не запрашиваются. Именно по этой причине одно из требований Apple заключается в необходимости предоставить пользователю в UI приложения возможность отключить обработку всех сетей либо конкретной сети (по SSID).
Следует отметить, что какого-либо надежного способа определить наличие другого Hotspot Helper в системе нет. Единственное, что можно сделать, — проверить, выбран ли в текущий момент Hotspot Helper основным для активной сети. Это можно сделать так:
let network = NEHotspotHelper.supportedNetworkInterfaces().first if !network.isChosenHelper { // Hotspot Helper не обрабатывает активную сеть }
Заметьте, false в этом флаге может означать, что для сети выбран другой Helper или даже что пока не выбран никакой (например, в процессе evaluate). Кроме того, сеть может уже появиться, но обработка подключения еще не начаться. Такая ситуация описана выше.
- В процессе обработки команды существует возможность отправки как низкоуровневых, так и высокоуровневых запросов. Для этого у команды существуют следующие методы:
func createTCPConnection(_ endpoint: NWEndpoint) -> NWTCPConnection func createUDPSession(_ endpoint: NWEndpoint) -> NWUDPSession
А также расширение на NSMutableURLRequest:
func bind(to command: NEHotspotHelperCommand)
- С выходом iOS10.3, очевидно в целях экономии ресурсов, система начала производить самовольные отключения от сети при засыпании устройства с последующим переподключением к той же сети. Интервалы отключения-подключения не поддаются прогнозированию и зависят от текущего состояния системы, устройства и установленных на нём приложений. Интервалы могут быть самые разные: от долей секунд до нескольких часов. Более того, не удаётся найти какой-либо возможности отличить автоматическое переподключение к сети от выполненного самим пользователем — для устройства они оба происходят одинаково.
Если у вас есть идеи как можно такие ситуации отличить или хотябы спрогнозировать, поделитесь ими, пожалуйста.
- С выходом iOS 11 функционал NEHotspotHelper дополнит новый класс NEHotspotConfigurationManager, с помощью которого можно подключаться к Wi-Fi прямо из приложения. Больше не надо заставлять пользователя идти в чуждые для него настройки и выбирать сеть из огромного списка с замороченными названиями. Это полезно в двух ситуациях:
- для приложений, предоставляющих публичные сервисы (кафе, бизнес-центры и др.);
- для приложений, производящих конфигурацию устройств по их собственной Wi-Fi-сети (например, видеокамеры). Для этого можно временно подключиться к заданной сетке до момента выхода из приложения.
Заключение
Технология NEHotspotHelper, появившаяся несколько лет назад, не утратила своей актуальности и по сей день. Этот инструмент позволяет значительно улучшить и облегчить процесс пользования сетевыми сервисами. Здесь я рассмотрел основные принципы работы, способы применения и все шаги, которые следует предпринять для его эффективного использования. Кроме того, рассказал и о некоторых особенностях Helper, о которых тактично умалчивает документация.
Надеюсь, теперь вы имеете полное представление о том, для чего и как стоит использовать эту штуку в вашем проекте. Впрочем, если у вас возникли какие-либо вопросы, напишите, я готов ответить на них.
Полезные ссылки
- Пример реализации на GitHub
- Hotspot Network Subsystem Programming Guide
- Network Extension
- NEHotspotHelper reference
- WWDC’15 What's New in Network Extension and VPN и её конспект
- WWDC’17 Advances in Networking, Part 1
- Forum: How to cancel register an app as a Hotspot Helper (NEHotspotHelper)?
- Forum: Как получить entitlements через почту или через прямую ссылку на форму запроса
- Forum: Как показать UI
- Небольшая статья про HotspotHelper
Автор: AndreyGusev