Как стать автором
Обновить

Как отследить состояние сетевого соединения в Swift? Привет, нативная реализация, пока, Reachability

Время на прочтение5 мин
Количество просмотров8.9K
Автор оригинала: Aryaman Sharda
Отслеживание сетевого соединения в Swift
Отслеживание сетевого соединения в Swift

Рассмотрим нативное решение для мониторинга сетевого подключения на 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
Установка Network Link Conditioner

После чего вы сможете настраивать нужные вам параметры, как и на реальном устройстве здесь:

Пример настройки для Network Link Conditioner
Пример настройки для Network Link Conditioner

Полезные ресурсы

  • Статья с разбором примера приложения для отслеживания сетевого соединения.

  • Пример работы для Network Link Conditioner

  • Страница с дополнительными утилитами от Apple.


Больше историй, подходов к реализации и инструментов для iOS-разработчика можно найти в авторском канале об iOS-разработке.

Канал об iOS-разработке
Канал об iOS-разработке

Теги:
Хабы:
+3
Комментарии3

Публикации

Изменить настройки темы

Истории

Работа

iOS разработчик
23 вакансии
Swift разработчик
32 вакансии

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн