Trying to monitor network changes in iOS app with Network framework from apple.
import Foundation
import Network
protocol NWPathMonitorInterface {
var pathUpdateHandler: ((_ newPath: NWPath) -> Void)? {get set}
func start(queue: DispatchQueue)
func cancel()
}
extension NWPathMonitor: NWPathMonitorInterface {}
final class ReachabilityManager {
private(set) var isNetworkAvailable: Bool = false
private(set) var connectionType: NWInterface.InterfaceType?
private let queue: DispatchQueue
private var monitor: NWPathMonitorInterface?
public var onUpdateNetworkStatus: ((Bool) -> Void)?
static let shared = ReachabilityManager(monitor: NWPathMonitor(),
queue: DispatchQueue(label: "com.rapido.networkMonitoring"))
deinit {
stopMonitoring()
}
init(monitor: NWPathMonitorInterface,
queue: DispatchQueue) {
self.monitor = monitor
self.queue = queue
}
enum ConnectionType {
case wifi
case cellular
case ethernet
}
public func startMonitoring() {
if monitor == nil {
monitor = NWPathMonitor()
}
self.monitor?.start(queue: queue)
self.monitor?
.pathUpdateHandler = { [weak self] path in
guard let self = self else { return }
self.updateStatus(status: path.status)
self.updateConnectionType(interface: path
.availableInterfaces
.map(\.type))
}
}
func updateStatus(status: NWPath.Status) {
isNetworkAvailable = status == .satisfied ? true : false
onUpdateNetworkStatus?(isNetworkAvailable)
}
func updateConnectionType(interface: [NWInterface.InterfaceType]) {
connectionType = interface.first
}
func stopMonitoring() {
monitor?.cancel()
monitor = nil
}
}
Start Monitoring when application state change to foreground and stop monitoring when application enter background
func applicationDidBecomeActive(_ application: UIApplication) {
ReachabilityManager.shared.startMonitoring()
}
func applicationDidEnterBackground(_ application: UIApplication) {
ReachabilityManager.shared.stopMonitoring()
}
Adding crash log
Crashed: com.apple.root.default-qos
0 libswiftCore.dylib 0x3da1bc _swift_release_dealloc + 32
1 libswiftNetwork.dylib 0x372a4 closure #1 in NWPathMonitor.init(requiredInterfaceType:) + 296
2 libswiftNetwork.dylib 0x2470 thunk for @escaping @callee_guaranteed (@guaranteed OS_nw_path) -> () + 52
3 Network 0x91dad8 __nw_path_evaluator_call_update_handler_block_invoke + 336
4 libdispatch.dylib 0x24b4 _dispatch_call_block_and_release + 32
5 libdispatch.dylib 0x3fdc _dispatch_client_callout + 20
6 libdispatch.dylib 0x70c8 _dispatch_queue_override_invoke + 788
7 libdispatch.dylib 0x15a6c _dispatch_root_queue_drain + 396
8 libdispatch.dylib 0x16284 _dispatch_worker_thread2 + 164
9 libsystem_pthread.dylib 0xdbc _pthread_wqthread + 228
10 libsystem_pthread.dylib 0xb98 start_wqthread + 8
One strange thing about crash happen only in iOS 16+ devices and 23% background state. I am not able to reproduce this still locally as frequency is very low. Any help is appreciated.
It seems somehow instance of NWMonitor
get dealloc in some edge although not replicated to me. Adding more sanity with isMonitoring
flag. Now it is 100% crash free.
Updated code
import Network
protocol NWPathMonitorInterface {
var pathUpdateHandler: ((_ newPath: NWPath) -> Void)? {get set}
func start(queue: DispatchQueue)
func cancel()
var currentPath: NWPath { get }
}
extension NWPathMonitor: NWPathMonitorInterface {}
final class ReachabilityManager {
private(set) var isNetworkAvailable: Bool = false
private(set) var connectionType: NWInterface.InterfaceType?
private(set) var isMonitoring: Bool = false
private let queue: DispatchQueue
private var monitor: NWPathMonitorInterface?
public var onUpdateNetworkStatus: ((Bool) -> Void)?
static let shared = ReachabilityManager(monitor: NWPathMonitor(),
queue: DispatchQueue(label: "com.rapido.networkMonitoring"))
deinit {
stopMonitoring()
}
init(monitor: NWPathMonitorInterface,
queue: DispatchQueue) {
self.monitor = monitor
self.queue = queue
}
enum ConnectionType {
case wifi
case cellular
case ethernet
}
public func startMonitoring() {
guard !isMonitoring else {
return
}
if monitor == nil {
monitor = NWPathMonitor()
}
self.monitor?.start(queue: queue)
self.monitor?
.pathUpdateHandler = { [weak self] status in
guard let self = self,
let monitor = self.monitor else { return }
self.updateStatus(status: monitor.currentPath.status)
self.updateConnectionType(interface: monitor.currentPath
.availableInterfaces
.map(\.type))
}
isMonitoring = true
}
func updateStatus(status: NWPath.Status) {
isNetworkAvailable = status == .satisfied ? true : false
onUpdateNetworkStatus?(isNetworkAvailable)
}
func updateConnectionType(interface: [NWInterface.InterfaceType]) {
connectionType = interface.first
}
func stopMonitoring() {
monitor?.cancel()
monitor = nil
isMonitoring = false
}
}