Доступность сети — важный аспект для приложений, полагающихся на сетевые возможности. Ваши пользователи не всегда будут иметь хорошее подключение к Интернету, поэтому оптимизация приложения для плохих сетевых условий очень важна.
Мы можем использовать несколько методов для соответствующей оптимизации нашего приложения, но очень важно знать о распространенных ошибках, когда вы это делаете. После написания нескольких сетевых фреймворков и десятков приложений, пришло время поделиться с вами моими лучшими практиками, чтобы предотвратить ошибки, которые я совершил в прошлом.
Ждать подключения вместо предварительной проверки на доступность
Самая распространенная ошибка, которую я вижу у разработчиков, — это предварительная проверка доступности перед выполнением запроса. Сетевые фреймворки Apple оптимизированы для так называемой модели Wait for Connectivity: если активный запрос сталкивается с отсутствием связи, он будет ждать и автоматически продолжит выполнение, когда связь восстановится.
Ваша проверка достижимости сети проходит в момент времени и может не отражать того, что действительно происходит в соединении. Ваш пользователь может находиться в лифте в течение нескольких секунд, при этом его соединение вскоре будет восстановлено. Если бы ваше приложение не решило проверить доступность в этот момент, оно бы выиграло от восстановления соединения, когда пользователь выйдет из лифта.
Вместо этого я рекомендую оптимизировать ваш сетевой уровень для модели Wait for Connectivity. Например, вы можете настроить таймауты в конфигурации URLSessionConfiguration:
let configuration = URLSessionConfiguration.default configuration.waitsForConnectivity = true configuration.timeoutIntervalForRequest = 60 // 1 minute configuration.timeoutIntervalForResource = 60 * 60 // 1 hour
В зависимости от типа приложения, вы можете настроить эти значения в соответствии с вашими потребностями. Возможно, вы не захотите ждать более 1 минуты для простого API-запроса, но ожидаете больше времени для запроса на загрузку. Булево waitsForConnectivity должно быть установлено в true и указывает, должна ли сессия ждать, пока соединение станет доступным, или же она должна быть немедленно завершена.
timeoutIntervalForRequest против timeoutIntervalForResource
Оба свойства выглядят похоже, но имеют разное назначение.
timeoutIntervalForRequest предназначен для обычных задач и контролирует, как долго (в секундах) задача должна ждать дополнительных данных. Обратите внимание, что соответствующий таймер для этого таймаута сбрасывается при поступлении новых данных. Другими словами, при медленном сетевом соединении вы все равно будете получать данные и, скорее всего, не достигнете этого таймаута.
timeoutIntervalForResource управляет тем, как долго (в секундах) ждать полной передачи ресурса, прежде чем сдаться. Значение по умолчанию установлено на 7 дней и может заставить вас задуматься: обычные запросы API не должны занимать так много времени. Вы правы, и это должно направить вас в правильном направлении. Этот таймаут предназначен для более длительных задач, таких как загрузка и выгрузка, которые выполняются в фоновой URL-сессии. Эти сеансы автоматически повторяют неудачные задачи за кадром, пока не будет достигнут timeoutIntervalForResource.
Если вы работаете с фоновыми URL-сессиями, вам стоит прочитать статью “URL-сессии: известные подводные камни при выполнении фоновых задач загрузки и выгрузки”.
Ограничение приложения только WiFi
Подобно предварительной проверке достижимости сети, многие приложения предварительно проверяют тип подключения перед выполнением любых запросов. Типичным сценарием является ограничение сетевого взаимодействия только для использования WiFi/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. Используя эти свойства вместо проверок Network Reachability, вы позволяете Apple выполнять оптимизацию для любых исходящих сетевых запросов.
Реагирование на ошибки сетевого подключения
Проверки досягаемости сети часто проводятся для того, чтобы сообщить пользователям, что их приложение не работает из-за потери соединения. Не проверяя достижимость, вы все равно можете обновить свой пользовательский интерфейс на основе любых возвращаемых ошибок.
Мне нравится сравнивать любые ошибки с этим списком кодов:
/// 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) } }
Обновляйте пользовательский интерфейс на основе свойства isOtherConnectionError всякий раз, когда один из ваших API-запросов возвращает ошибку, чтобы сообщить пользователям о нестабильном соединении. Например, в WeTransfer мы показываем пользователю sheet, объясняющий, что его соединение нестабильно — скриншот вы можете видеть выше.
Когда следует использовать проверку достижимости сети
На данном этапе вы изо всех сил пытались выполнить сетевые запросы, независимо от соединения. Вы получили ошибку, указывающую на нестабильную сетевую среду, и, возможно, захотите повторить запрос автоматически, как только соединение будет восстановлено.
В этот момент допустимо начать проверку восстановления сетевого соединения. Рекомендуемый способ сделать это — использовать 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, Cellular и Airplane режимами, но часто для ускорения процесса разработки вы хотите провести тестирование на симуляторе.
К сожалению, единственный способ отключить сеть на симуляторе — это выключить WiFi на вашем Mac. Это означает, что вы не сможете использовать другие приложения или искать ответы на Stack Overflow во время отладки вашей реализации. Apple предоставляет Network Link Conditioner, который также влияет на сетевое подключение всей вашей системы.
Отключение сетевого подключения только для симулятора
Я решил эту проблему, создав сетевое расширение для RocketSim. После некоторой отладки сетевого расширения на macOS мне удалось создать среду, в которой определенная сборка симулятора будет заблокирована от подключения к сети:
В приведенном выше примере видно, что я пытаюсь создать новую передачу данных. Запрос начинается, но сразу после того, как он сталкивается с потерей соединения, происходит сбой. После того как я убедился, что приложение корректно отвечает обновлением для пользователя, я восстановил соединение, отключив режим самолета.
Вы можете загрузить RocketSim из Mac App Store и попробовать авиарежим для своего приложения.
Заключение
Оптимизация вашего приложения для сетевой доступности начинается с предотвращения распространенных ошибок, таких как предварительная проверка доступности перед выполнением запроса. Как только вы столкнулись с отсутствующим соединением, вы можете проверить наличие восстановленного соединения и возобновить запросы соответствующим образом. Используя RocketSim, вы сможете проверить свою реализацию без влияния на соединение вашего Mac.