Рассмотрим нативное решение для мониторинга сетевого подключения на iOS с помощью Swift 5 и использования Network Link Conditioner
.
Примечание
Если вы найдёте статью интересной, то в этом канале я пишу об iOS-разработке.
Большинство реализаций, которые вы можете найти для мониторинга сетевого подключения вашего iOS-устройства, основаны на использовании сторонних зависимостей, таких как Reachability, NetworkReachabilityManager
в Alamofire
, или же утилит, которые периодически шлют HTTP-запросы для определения статуса сетевого подключения.
Вместо этого я хотел бы представить альтернативный подход, который использует нативный фреймворк, представленный в iOS 12.
Для этой реализации нам понадобится только фреймворк Network
. Хотя вы обычно используете его, когда вам нужен прямой доступ к таким протоколам, как TLS, TCP и UDP, мы не будем делать здесь ничего слишком сложного.
Начальная реализация
Давайте начнем создание нашей утилиты NetworkMonitor:
import Network
final class NetworkMonitor {
static let shared = NetworkMonitor()
private let monitor: NWPathMonitor
private init() {
monitor = NWPathMonitor()
}
}
Здесь NWPathMonitor
является наблюдателем, который будет отслеживать состояние сетевого соединения и реагировать на изменения, которые могут произойти.
Далее, мы создадим несколько свойств для хранения текущего состояния сетевого соединения:
final class NetworkMonitor {
static let shared = NetworkMonitor()
private let monitor: NWPathMonitor
private(set) var isConnected = false
/// Следующее свойство нужно для проверки, что сетевое соединение
/// будет дорогим в плане потребления трафика
///
/// Сотовые интерфейсы считаются дорогими. WiFi точки доступа
/// от других девайсов также могут быть дорогими. Другие интерфейсы
/// могут оказаться дорогими в будущем
private(set) var isExpensive = false
/// curentConnectionType указывает на тип текущего соединения
/// в сети, к которой мы подключены
///
/// Возможные состояния могут быть `other`, `wifi`, `cellular`,
/// `wiredEthernet`, or `loopback`
private(set) var currentConnectionType: NWInterface.InterfaceType?
private init() {
monitor = NWPathMonitor()
}
}
Поскольку эти свойства могут быть только read-only, используется
private(set)
.
Мы явно не хотим, чтобы эта длительная задача выполнялась в главном потоке нашего приложения, поэтому создадим новую очередь DispatchQueue для управления этой задачей:
private let queue = DispatchQueue(label: "NetworkConnectivityMonitor")
Сетевой фреймворк определяет перечисление NWInterface.InterfaceType
, которое содержит все типы медиа, которые может поддерживать наше устройство (WiFi, сотовая связь, проводной ethernet и т.д.).
Поскольку это перечисление объявлено в ObjC
, у нас нет доступа к свойству allCases
, как, например, в случае с перечислениями в Swift. Поэтому добавим соответствие протоколу CaseIterable
и реализуем allCases
. В результате этого дополнительного шага остальная часть нашей реализации будет намного проще и гораздо более читабельнее.
extension NWInterface.InterfaceType: CaseIterable {
public static var allCases: [NWInterface.InterfaceType] = [
.other,
.wifi,
.cellular,
.loopback,
.wiredEthernet
]
}
Последним шагом в нашей реализации является создание функций, отвечающих за запуск и остановку процесса мониторинга:
func startMonitoring() {
monitor.pathUpdateHandler = { [weak self] path in
self?.isConnected = path.status != .unsatisfied
self?.isExpensive = path.isExpensive
// Identifies the current connection type from the
// list of potential network link types
self?.currentConnectionType = NWInterface.InterfaceType.allCases.filter { path.usesInterfaceType($0) }.first
}
monitor.start(queue: queue)
}
func stopMonitoring() {
monitor.cancel()
}
NetworkMonitor в действии
Отслеживание можно запустить из любой точки кода, просто вызвав NetworkMonitor.shared.startMonitoring()
, хотя в большинстве случаев вы захотите инициировать этот процесс в AppDelegate
. Затем мы можем использовать NetworkMonitor.shared.isConnected
для проверки состояния нашего сетевого соединения в режиме реального времени.
Вот наша реализация на данный момент:
import Network
extension NWInterface.InterfaceType: CaseIterable {
public static var allCases: [NWInterface.InterfaceType] = [
.other,
.wifi,
.cellular,
.loopback,
.wiredEthernet
]
}
final class NetworkMonitor {
static let shared = NetworkMonitor()
private let queue = DispatchQueue(label: "NetworkConnectivityMonitor")
private let monitor: NWPathMonitor
private(set) var isConnected = false
private(set) var isExpensive = false
private(set) var currentConnectionType: NWInterface.InterfaceType?
private init() {
monitor = NWPathMonitor()
}
func startMonitoring() {
monitor.pathUpdateHandler = { [weak self] path in
self?.isConnected = path.status != .unsatisfied
self?.isExpensive = path.isExpensive
self?.currentConnectionType = NWInterface.InterfaceType.allCases.filter { path.usesInterfaceType($0) }.first
}
monitor.start(queue: queue)
}
func stopMonitoring() {
monitor.cancel()
}
}
Добавление поддержки NotificationCenter
Поведение современных приложений для iOS резко меняется при отключении сетевого соединения устройства — на некоторых экранах может появиться уведомление о том, что устройство потеряло соединение, может измениться поведение кэширования приложения или даже нарушатся некоторые пользовательские сценарии.
Для поддержки такого типа поведения нам необходимо расширить нашу реализацию, чтобы отправлять уведомления по всему приложению при изменении статуса подключения.
import Foundation
import Network
extension Notification.Name {
static let connectivityStatus = Notification.Name(rawValue: "connectivityStatusChanged")
}
extension NWInterface.InterfaceType: CaseIterable {
public static var allCases: [NWInterface.InterfaceType] = [
.other,
.wifi,
.cellular,
.loopback,
.wiredEthernet
]
}
final class NetworkMonitor {
static let shared = NetworkMonitor()
private let queue = DispatchQueue(label: "NetworkConnectivityMonitor")
private let monitor: NWPathMonitor
private(set) var isConnected = false
private(set) var isExpensive = false
private(set) var currentConnectionType: NWInterface.InterfaceType?
private init() {
monitor = NWPathMonitor()
}
func startMonitoring() {
monitor.pathUpdateHandler = { [weak self] path in
self?.isConnected = path.status != .unsatisfied
self?.isExpensive = path.isExpensive
self?.currentConnectionType = NWInterface.InterfaceType.allCases.filter { path.usesInterfaceType($0) }.first
NotificationCenter.default.post(name: .connectivityStatus, object: nil)
}
monitor.start(queue: queue)
}
func stopMonitoring() {
monitor.cancel()
}
}
// ViewController.swift
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(showOfflineDeviceUI(notification:)), name: NSNotification.Name.connectivityStatus, object: nil)
}
@objc func showOfflineDeviceUI(notification: Notification) {
if NetworkMonitor.shared.isConnected {
print("Connected")
} else {
print("Not connected")
}
}
}
Весь исходный код находится здесь.
Network Link Conditioner
Раз уж мы заговорили о сетевых технологиях и отладке проблем с подключением, самое время упомянуть о Network Link Conditioner.
С помощью этого инструмента можно имитировать различные сетевые условия на компьютере и, соответственно, в симуляторе iOS. С помощью этого инструмента мы можем не только отслеживать экстремальные ситуации, когда мы полностью онлайн или оффлайн, но и тестировать поведение нашего приложения в различных сетевых условиях.
Вы можете загрузить его с сайта разработчиков Apple или по этой ссылке.
Сама утилита будет находиться в папке Hardware, для установки просто кликните по ней.
После чего вы сможете настраивать нужные вам параметры, как и на реальном устройстве здесь:
Полезные ресурсы
Статья с разбором примера приложения для отслеживания сетевого соединения.
Пример работы для Network Link Conditioner
Страница с дополнительными утилитами от Apple.
Больше историй, подходов к реализации и инструментов для iOS-разработчика можно найти в авторском канале об iOS-разработке.
Комментарии (2)
YuriyPashkov
21.04.2022 09:24Как в данной реализации отслеживать ситуацию с подключением к Wi-Fi, в котором нет доступа к интернету?
storoj
NotificationCenter
кажется разумным улучшением, но это стремление что-то куда-то непременно заинкапсулировать регулярно вызывает во мне печаль.Почему не использовать
NWMonitor
напрямую? В Apple уже и так достаточно постарались, чтобы предоставить высокоуровневый интерфейс для решения задачи отслеживания состояния соединения. Зачем этот дополнительный объект, который ограничивает доступный API, и только и делает, что проксирует свой публичный интерфейс в инстансNWMonitor
? Причём даже не скрывает того, что работает сNetwork Framework
, т.к. возвращаетNWInterface.InterfaceType
. Зачем усложнять себе жизнь, делая эти бесконечные врапперы и менеджеры?Я бы сократил весь код до: