Skip to content

Instantly share code, notes, and snippets.

@ivanopcode
Created June 20, 2023 19:35
Show Gist options
  • Save ivanopcode/06be8d296b0034214588ee708ff48af5 to your computer and use it in GitHub Desktop.
Save ivanopcode/06be8d296b0034214588ee708ff48af5 to your computer and use it in GitHub Desktop.
Thread-Safe Network Status Monitor using Swift Actors and Combine
// MIT
// by Alexey Grigorev, Ivan Oparin
// A thread-safe service for subscribing to network status updates on Apple's platforms. It
// utilizes Apple's Network framework to monitor network conditions. The service
// uses the actor model for thread safety, protecting from data
// races. Updates are emitted via Combine publishers, for easy integration with
// reactive or async code. Updates include network connection status, if the
// connection is expensive, and if the status has changed."
import Foundation
import Combine
import Network
public struct NetworkStatus: Equatable {
public let connected: Bool?
public let expensive: Bool?
public let wasChanged: Bool?
public init(
connected: Bool? = nil, expensive: Bool? = nil, wasChanged: Bool? = nil
) {
self.connected = connected
self.expensive = expensive
self.wasChanged = wasChanged
}
}
actor NetworkStatusService: ObservableObject {
private let monitor = NWPathMonitor()
private let queue = DispatchQueue(label: "NetworkStatusService", qos: .default)
private(set) var networkStatus: NetworkStatus = .init()
private let networkSub = CurrentValueSubject<NetworkStatus, Never>(.init())
public var networkStatusPublisher: AnyPublisher<NetworkStatus, Never> {
networkSub
.removeDuplicates()
.eraseToAnyPublisher()
}
public func start() {
startWatchNetworkCondition()
}
private func startWatchNetworkCondition() {
// let t1 = Date()
monitor.pathUpdateHandler = { [weak self] path in
guard let strongSelf = self else {
return
}
Task {
let prevStatus = await strongSelf.getStatus()
let nextStatus = await strongSelf.buildStatus(path: path, prevStatus: prevStatus)
if prevStatus != nextStatus {
// log network status
}
await strongSelf.updateStatus(newStatus: nextStatus)
strongSelf.networkSub.send(nextStatus)
}
}
monitor.start(queue: queue)
// let t2 = t1.distance(to: Date())
// log performance
}
private func buildStatus(path: NWPath, prevStatus: NetworkStatus) -> NetworkStatus {
let isExpensive = path.isExpensive
let isConnected = path.status == .satisfied
let wasChanged = prevStatus.connected != isConnected
return .init(
connected: isConnected,
expensive: isExpensive,
wasChanged: wasChanged
)
}
private func getStatus() -> NetworkStatus {
return networkStatus
}
private func updateStatus(newStatus: NetworkStatus) {
networkStatus = newStatus
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment