Проверка статуса сети iOS: границы возможностей NWPathMonitor
Пользователь нажимает кнопку обновления, приложение сообщает «Сеть недоступна», но повторная попытка чуть позже проходит успешно. Часто проблема не в механизме отправки запросов: приложение использует ещё не обновившееся состояние сетевого пути как условие допуска к запросу. При разработке iOS-приложения на облачном Mac сначала можно проверить модель состояний с помощью детерминированной последовательности событий, а симулятор и физическое устройство оставить для проверки поведения системы. Для этого не нужно раз за разом обрывать удалённое подключение, пытаясь воспроизвести сбой.
Разделите подсказки о сети и результаты запросов
NWPathMonitor отслеживает сетевые пути, соответствующие заданным условиям мониторинга. Состояние .satisfied не гарантирует ни доступность сервера, ни действительность учётных данных; .requiresConnection означает, что требуется установить соединение, а не то, что устройство окончательно осталось без сети.
На уровне приложения стоит различать как минимум четыре состояния: «неизвестно», «путь доступен», «требуется соединение» и «путь недоступен». При первом запуске, пока обратный вызов ещё не получен, показывайте неизвестное состояние, а не считайте сеть недоступной по умолчанию. Тайм-ауты запросов, ошибки аутентификации и ошибки сервера должны попадать в отдельную модель результатов запросов, не перезаписывая состояние сетевого пути.
Состояние сети подходит для подсказок, но не для общего переключателя «запретить все запросы». Если пользователь сам запускает повторную попытку, её исход должен определяться реальным запросом.
Такой подход полезен приложениям с баннером состояния сети и подсказками об автоматических повторных попытках. Если же функция просто отправляет один запрос, обычно проще обработать его результат напрямую, не добавляя отдельный мониторинг.
Создайте модель состояний, работающую независимо от сети
В командной строке удалённого macOS создайте файл NetworkStateCheck.swift и поместите в него весь приведённый ниже код. Модель не зависит от реальной сети, а номер поколения позволяет различать экземпляры монитора. В примере вызовы выполняются последовательно; изменять модель напрямую из нескольких очередей одновременно нельзя.
enum PathState: Equatable {
case unknown, available, needsConnection, unavailable
}
struct NetworkHint {
private(set) var generation = 0
private(set) var state: PathState = .unknown
private(set) var expensive = false
private(set) var constrained = false
mutating func restart() -> Int {
generation += 1
state = .unknown
expensive = false
constrained = false
return generation
}
mutating func accept(
generation incoming: Int,
state next: PathState,
expensive: Bool = false,
constrained: Bool = false
) {
guard incoming == generation else { return }
state = next
self.expensive = expensive
self.constrained = constrained
}
}
var hint = NetworkHint()
precondition(hint.state == .unknown)
let old = hint.restart()
hint.accept(generation: old, state: .available, expensive: true)
precondition(hint.state == .available && hint.expensive)
let current = hint.restart()
hint.accept(generation: old, state: .unavailable)
precondition(hint.state == .unknown)
hint.accept(generation: current, state: .needsConnection)
precondition(hint.state == .needsConnection)
hint.accept(generation: current, state: .available, constrained: true)
precondition(hint.constrained && !hint.expensive)
hint.accept(generation: current, state: .unavailable)
precondition(hint.state == .unavailable)
hint.accept(generation: current, state: .available)
precondition(hint.state == .available && !hint.constrained)
Выполните xcrun swift NetworkStateCheck.swift. При успешной проверке вывода не будет; при ошибке процесс завершится с ненулевым кодом. Скрипт проверяет контракт состояний самого приложения, а не корректность системного сетевого стека.
Соблюдайте порядок действий при подключении монитора
В адаптере для рабочего приложения импортируйте Network, создайте NWPathMonitor(), сначала задайте pathUpdateHandler и только затем вызовите start(queue:). Сопоставьте три значения NWPath.Status со значениями перечисления модели и передавайте вместе с ними isExpensive и isConstrained. Неизвестные значения перечисления, которые могут появиться в будущем, должны соответствовать неизвестному состоянию.
При создании монитора захватите текущий номер поколения. Перед передачей обратного вызова в модель состояний снова проверьте поколение. При перезапуске мониторинга сначала увеличьте номер поколения, чтобы старые события утратили силу, затем отмените старый экземпляр и только после этого создайте новый. Не запускайте повторно экземпляр, который уже был отменён.
Управление жизненным циклом монитора и обновление модели должны выполняться в одном последовательном контексте исполнения. Если вы используете главный контекст исполнения, проверяйте поколение уже после перехода в него из обратного вызова. Иначе старые события, ожидающие в очереди, всё ещё смогут испортить новое состояние. Сохраняйте этот порядок и при передаче обновлений интерфейсу: несколько асинхронных отправок не должны перезаписывать состояние в обратном порядке.
Не определяйте наличие сети на устройстве только с помощью requiredInterfaceType: .wifi: такой монитор отслеживает пути, соответствующие условию по конкретному интерфейсу, и может не учитывать другие доступные пути. Используйте этот способ инициализации, только если функция действительно должна работать через определённый интерфейс.
Проверьте граничные случаи по матрице событий
Перенесите скрипт в целевой модуль юнит-тестов проекта. Для каждого теста создавайте отдельную модель и добавьте как минимум следующие проверки:
- После запуска нет обратного вызова: состояние остаётся неизвестным, запросы по-прежнему можно отправлять.
- Путь доступен, но запрос к API завершается ошибкой: обновляется только ошибка запроса, разрыв сетевого пути не показывается.
- Недоступный путь снова становится доступным: баннер исчезает, одно и то же действие приложения не запускается повторно.
- После отмены приходит старое событие: ни состояние, ни подсказка о стоимости передачи данных не меняются.
- Одно и то же событие приходит несколько раз: подсказка остаётся прежней, задачи повторных попыток не накапливаются.
- Меняется признак высокой стоимости или ограниченного режима передачи данных: корректируется только стратегия загрузки, которую допускает логика приложения.
isExpensive не обозначает один конкретный тип интерфейса, а isConstrained не означает полное отсутствие сети. Для передачи больших файлов можно использовать настройки URLSessionConfiguration, регулирующие работу в дорогих и ограниченных сетях. Не отбрасывайте действия пользователя без уведомления только на основании модели подсказок.
Разграничьте автоматические тесты и проверку на устройстве
Симулятор подходит для проверки баннеров, кнопок и подключения модели состояний, но его сетевое поведение зависит от хост-компьютера. Изменение подсказки в симуляторе не доказывает, что переключение на сотовую сеть проверено. Тем более не отключайте сетевые интерфейсы облачного Mac ради теста: так можно потерять подключение к удалённому рабочему столу.
На физическом устройстве дополнительно проверьте переключение беспроводных сетей, ограниченный режим передачи данных и возврат из фона. Для каждого сценария зафиксируйте порядок событий сетевого пути, изменения интерфейса и результаты запросов. Отказ тестового сервера в соединении и недоступность сетевого пути — разные сценарии; результаты их проверки нужно сохранять отдельно.
В итоге передайте модель состояний, тесты событий и записи проверок на устройстве. Критерий приёмки не в том, чтобы баннер всегда мгновенно угадывал состояние сети. Важно, чтобы подсказки не блокировали запросы по ошибке, старый монитор не перезаписывал состояние нового сеанса, а действия пользователя после сбоя обрабатывались предсказуемо.
Часто задаваемые вопросы
Гарантирует ли satisfied доступ к API?
Нет. Статус означает наличие пути, соответствующего условиям наблюдения, но не проверяет сервер, авторизацию или содержимое ответа. Успех определяется результатом запроса.
Как отсечь старые события после отмены?
Сначала увеличьте поколение в том же последовательном контексте, затем отмените старый монитор. Принимайте только события текущего поколения. Для повторного запуска создавайте новый монитор.
Проверьте удалённый Mac на своих задачах
Арендуйте отдельный Mac mini M4 и работайте в macOS через графический интерфейс или командную строку. Подходит, чтобы проверить подключение и рабочий процесс. Для длительных автономных задач или профессионального внешнего оборудования сравните с покупкой собственного Mac.
Арендовать отдельный Mac mini