Skip to content
Snippets Groups Projects
NetworkMonitor.swift 2.69 KiB
Newer Older
Bruno Muniz's avatar
Bruno Muniz committed
// https://www.reddit.com/r/swift/comments/ir8wn5/network_connectivity_is_always_unsatisfied_when/

import Network
import Combine
import Foundation
Bruno Muniz's avatar
Bruno Muniz committed

public enum NetworkStatus: Equatable {
    case unknown
    case available
    case xxNotAvailable
    case internetNotAvailable
}

public enum ConnectionType {
    case wifi
    case ethernet
    case cellular
    case unknown
}

Bruno Muniz's avatar
Bruno Muniz committed
public protocol NetworkMonitoring {
    func start()
    func update(_ status: Bool)

    var xxStatus: NetworkStatus { get }
    var connType: AnyPublisher<ConnectionType, Never> { get }
Bruno Muniz's avatar
Bruno Muniz committed
    var statusPublisher: AnyPublisher<NetworkStatus, Never> { get }
}

public struct NetworkMonitor: NetworkMonitoring {
    public init() {}

    private var monitor = NWPathMonitor()
    private let isXXAvailableRelay = CurrentValueSubject<Bool?, Never>(nil)
    private let isInternetAvailableRelay = CurrentValueSubject<Bool?, Never>(nil)
    private let connTypeSubject = PassthroughSubject<ConnectionType, Never>()
Bruno Muniz's avatar
Bruno Muniz committed

    public var xxStatus: NetworkStatus {
        isXXAvailableRelay.value == true ? .available : .xxNotAvailable
    }

    public var connType: AnyPublisher<ConnectionType, Never> {
        connTypeSubject.eraseToAnyPublisher()
    }

Bruno Muniz's avatar
Bruno Muniz committed
    public var statusPublisher: AnyPublisher<NetworkStatus, Never> {
        isInternetAvailableRelay.combineLatest(isXXAvailableRelay)
            .map { (isInternetAvailable, isXXAvailable) -> NetworkStatus in

                guard let isInternetAvailable = isInternetAvailable,
                      let isXXAvailable = isXXAvailable else { return .unknown }

                switch (isInternetAvailable, isXXAvailable) {
                case (true, true):
                    return .available
                case (true, false):
                    return .xxNotAvailable
                case (false, _):
                    return .internetNotAvailable
                }
            }
            .removeDuplicates()
            .eraseToAnyPublisher()
    }

    public func start() {
        monitor.pathUpdateHandler = { [weak isInternetAvailableRelay, weak connTypeSubject] in
            connTypeSubject?.send(checkConnectionTypeForPath($0))
Bruno Muniz's avatar
Bruno Muniz committed
            isInternetAvailableRelay?.send($0.status == .satisfied)
        }

        monitor.start(queue: .global())
    }

    public func update(_ status: Bool) {
        isXXAvailableRelay.send(status)
    }

    private func checkConnectionTypeForPath(_ path: NWPath) -> ConnectionType {
        if path.usesInterfaceType(.wifi) {
            return .wifi
        } else if path.usesInterfaceType(.wiredEthernet) {
            return .ethernet
        } else if path.usesInterfaceType(.cellular) {
            return .cellular
        }

        return .unknown
    }
Bruno Muniz's avatar
Bruno Muniz committed
}