Created
June 20, 2023 19:35
-
-
Save ivanopcode/06be8d296b0034214588ee708ff48af5 to your computer and use it in GitHub Desktop.
Thread-Safe Network Status Monitor using Swift Actors and Combine
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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