Сетевая доступность является крайне важным аспектом приложений, использующих некоторые возможности сети. У ваших пользователей не всегда будет хорошее интернет-соединение, поэтому оптимизация приложения для плохих условий сети имеет важное значение.
Для соответствующей оптимизации нашего приложения мы можем использовать несколько способов, но при этом крайне важно знать о распространенных ошибках. После самостоятельного написания нескольких Сетевых фреймворков и десятков приложений, самое время поделиться с вами наилучшими решениями, и не дать вам допустить ошибки, которые я сделал в прошлом.
Ждите подключения вместо предварительной проверки доступности
Самой распространенной ошибкой, которую я вижу среди разработчиков, является предварительная проверка доступности перед отправкой запроса. Сетевые фреймворки Apple оптимизированы под так называемую модель "Ожидания подключения": если активный запрос сталкивается с отсутствием подключения, он будет ждать и автоматически продолжит выполнение после восстановления соединения.
Ваша проверка доступности валидирует какой-то момент времени и может не отражать того, что происходит с соединением. Пользователь может несколько секунд находиться в лифте, и соединение вот-вот восстановится. Но если ваше приложение решило выполнить запрос, оно бы выиграло от восстановленного после выхода пользователя из лифта соединения.
Вместо этого я рекомендую оптимизировать сетевой уровень под модель "Ожидания подключения". Например, вы можете настроить тайм-ауты в URLSessionConfiguration:
let configuration = URLSessionConfiguration.default
configuration.waitsForConnectivity = true
configuration.timeoutIntervalForRequest = 60 // 1 minute
configuration.timeoutIntervalForResource = 60 * 60 // 1 hour
В зависимости от типа приложения, вы можете скорректировать эти значения под ваши требования. Может быть для простого API-запроса вам не захочется ждать больше 1 минуты, но для запроса на выгрузку заложите больше времени. waitsForConnectivity должен быть установлен в значение true и указывать, должна ли сессия ожидать доступности соединения или сразу же завершаться с ошибкой.
timeoutIntervalForRequest и timeoutIntervalForResource
Похожи друг на друга, но имеют различные цели.
timeoutIntervalForRequest применяется для обычных задач и управляет тем, как долго (в секундах) задача должна ждать дополнительных данных. Обратите внимание, что связанный с ним таймер сбрасывается при поступлении новых данных. Другими словами, при медленных сетевых соединениях вы все равно будете получать данные и, вероятно, не достигнете тайм-аута.
timeoutIntervalForResource контролирует, как долго (в секундах) ожидать передачи полного ресурса, прежде чем “сдаться”. Значение по умолчанию установлено на 7 дней и возможно вы зададитесь вопросом: обычные API-запросы не должны занимать столько времени. Вы правы, и это должно указать вам на правильное направление. Этот тайм-аут предназначен для более длительных задач, таких как выгрузки и загрузки, которые выполняются в фоновой URL-сессии. Эти сессии автоматически повторяют неудавшиеся задачи в фоне, пока не будет достигнут timeoutIntervalForResource.
Если вы работаете с фоновыми URL-сессиями, возможно вас заинтересует "URL-Сессии: Распространенные ошибки при работе с фоновой загрузкой и выгрузкой."
Ограничение работы приложения только по Wi-Fi
Подобно предварительной проверке доступности сети, многие приложения выполняют предварительную проверку типа подключения перед выполнением любых запросов. Типичный сценарий - ограничение сетевого взаимодействия только с использованием Wi-Fi / Ethernet соединений.
Вместо этого лучше настроить сетевой уровень так, чтобы были разрешены только определенные типы подключения. Вы можете сделать это, скорректировав параметры вашей URLSessionConfiguration:
let configuration = URLSessionConfiguration.default
/// Set to `false` to only allow WiFi/Ethernet.
configuration.allowsCellularAccess = true
/// Set to `false` to prevent your app from using network interfaces that the system considers expensive.
configuration.allowsExpensiveNetworkAccess = true
/// Indicates whether connections may use the network when the user has specified Low Data Mode.
configuration.allowsConstrainedNetworkAccess = true
Вы можете изменить эти свойства в любое время, если у вас есть ссылка на экземпляр вашей URLSessionConfiguration. Используя эти свойства в обход проверок на Доступность Сети, вы позволяете Apple совершать оптимизации для любых исходящих сетевых запросов.
Обработка Сетевых Ошибок
Проверки Доступности Сети часто используются, чтобы говорить пользователям, что их приложение не работает из-за потери соединения. Но вfi UI все равно может обновляться на основе любых полученных ошибок и без проверки доступности.
Мне нравится сравнивать любые ошибки с этим списком кодов:
/// A collection of error codes that related to network connection failures.
public var NSURLErrorConnectionFailureCodes: [Int] {
[
NSURLErrorBackgroundSessionInUseByAnotherProcess, /// Error Code: `-996`
NSURLErrorCannotFindHost, /// Error Code: ` -1003`
NSURLErrorCannotConnectToHost, /// Error Code: ` -1004`
NSURLErrorNetworkConnectionLost, /// Error Code: ` -1005`
NSURLErrorNotConnectedToInternet, /// Error Code: ` -1009`
NSURLErrorSecureConnectionFailed /// Error Code: ` -1200`
]
}
Не все коды ошибок относятся к отсутствию интернет-соединения, но они дают мне повод сообщить пользователям о нестабильном соединении. Вы можете создать удобное расширение следующим образом:
extension Error {
/// Indicates an error which is caused by various connection related issue or an unaccepted status code.
/// See: `NSURLErrorConnectionFailureCodes`
var isOtherConnectionError: Bool {
NSURLErrorConnectionFailureCodes.contains(_code)
}
}
Обновляйте свой UI на основе свойства isOtherConnectionError каждый раз, когда один из ваших запросов API возвращает ошибку, чтобы сообщить вашим пользователям о нестабильном соединении. Например, в WeTransfer мы показываем пользователю окно с пояснением, что их соединение нестабильно:
Приложение WeTransfer объясняет и уведомляет своих пользователей о проблемах с подключением к сети.
Когда следует использовать проверки Доступности Сети
На данный момент вы сделали все возможное, чтобы выполнить сетевые запросы, независимо от подключения. Вы получили ошибку, указывающую на нестабильную сетевую среду, и вы, возможно, захотите повторить запрос автоматически после восстановления соединения.
На данный момент можно начать проверку восстановленного сетевого соединения. Рекомендуется сделать это, используя NWPathMonitor:
import Network
let pathMonitor = NWPathMonitor()
pathMonitor.pathUpdateHandler { path in
switch path.status {
case .satisfied:
// Networking connection restored
default:
// There's no connection available
}
}
Вышеуказанный пример кода представляет собой наиболее простую реализацию монитора, но существует еще много других опций, таких как проверка только на восстановление сотового подключения. Я рекомендую вам изучить API, если вам нужны дополнительные опции.
Тестирование вашего приложения на наличие сетевого подключения
При реализации поддержки Доступности Сети, очень важно создать стабильную тестовую среду. Вы можете запустить ваше приложение на реальном устройстве и переключаться между WiFi, Сотовым и Авиарежимом, но чтобы ускорить свой рабочий процесс, вы будете чаще проверять работу на Симуляторе.
К сожалению, единственный способ отключить сеть на симуляторе - это выключить WiFi на вашем Mac. Это приведет к тому, что вы не сможете пользоваться другими приложениями или искать решения проблем на Stack Overflow во время отладки вашей реализации. Apple предоставляет Network Link Conditioner, который также влияет на сетевое подключение всей вашей системы.
Отключение сети только для вашего Приложения на Симуляторе
Я решил эту проблему, создав Сетевое Расширение для RocketSim. После некоторой Отладки Сетевого Расширения на macOS мне удалось создать среду, в которой определенная версия Симулятора не будет иметь сетевое подключение:
https://www.avanderlee.com/wp-content/uploads/2023/04/airplane_mode_compressed.mp4
Airplane Mode в RocketSim блокирует сетевое подключение только для вашего Приложения в Симуляторе.
В приведенном выше примере видно, что я пытаюсь создать новую передачу. Запрос начинается, но сразу же завершается, как только сталкивается с потерей соединения. После того, как я убедился, что приложение, обновляясь, дает пользователю правильный ответ, я восстановил соединение, отключив режим самолета.
Вы можете скачать RocketSim из Mac App Store и попробовать Авиарежим для своего приложения.
Заключение
Оптимизация вашего приложения для проверки Доступности Сети начинается с предотвращения распространенных ошибок, таких как предварительная проверка доступности сети перед выполнением запроса. Когда вы сталкиваетесь с проблемой отсутствия подключения, то можете проверять восстановление подключения и исходя из этого возобновлять запросы. Используя RocketSim, вы можете проверить свою реализацию без воздействия на соединение вашего Mac.
Если вы хотите улучшить свои знания в Swift, загляните на страницу категорий Swift. Если у вас есть дополнительные советы или отзывы, не постесняйтесь связаться со мной или написать мне в Twitter.
Спасибо!