Files
simplex-chat/apps/ios/Shared/Model/NetworkObserver.swift
T
shum ffb0527755 ios: reconnect servers when network changes
iOS does not report the sockets of the previous network as broken, so
after switching between Wi-Fi and mobile the agent keeps using dead
sessions - setting network info does not drop them. Recovery only
happened when the OS finally failed the socket, via TCP keep-alive or
the SMP ping monitor, taking from ~90 seconds to tens of minutes.

Reconnect the servers on every network change after the first one, when
the network is online. Changes that keep the same network type, e.g.
switching between Wi-Fi networks, are not detected, as network info only
has the network type.
2026-08-17 12:56:35 +00:00

89 lines
2.7 KiB
Swift

//
// NetworkObserver.swift
// SimpleX (iOS)
//
// Created by Avently on 05.04.2024.
// Copyright © 2024 SimpleX Chat. All rights reserved.
//
import Foundation
import Network
import SimpleXChat
class NetworkObserver {
static let shared = NetworkObserver()
private let queue: DispatchQueue = DispatchQueue(label: "chat.simplex.app.NetworkObserver")
private var prevInfo: UserNetworkInfo? = nil
private var monitor: NWPathMonitor?
private let monitorLock: DispatchQueue = DispatchQueue(label: "chat.simplex.app.monitorLock")
func restartMonitor() {
monitorLock.sync {
monitor?.cancel()
let mon = NWPathMonitor()
mon.pathUpdateHandler = { [weak self] path in
self?.networkPathChanged(path: path)
}
mon.start(queue: queue)
monitor = mon
}
}
private func networkPathChanged(path: NWPath) {
let info = UserNetworkInfo(
networkType: networkTypeFromPath(path),
online: path.status == .satisfied
)
if (prevInfo != info) {
// iOS does not report the sockets of the previous network as broken, so the servers have to
// be reconnected. There is nothing to reconnect before the first network info is set.
let shouldReconnect = prevInfo != nil && info.online
prevInfo = info
setNetworkInfo(info)
if shouldReconnect { reconnectServers() }
}
}
private func networkTypeFromPath(_ path: NWPath) -> UserNetworkType {
if path.usesInterfaceType(.wiredEthernet) {
.ethernet
} else if path.usesInterfaceType(.wifi) {
.wifi
} else if path.usesInterfaceType(.cellular) {
.cellular
} else if path.usesInterfaceType(.other) {
.other
} else {
.none
}
}
private static var networkObserver: NetworkObserver? = nil
private func setNetworkInfo(_ info: UserNetworkInfo) {
logger.debug("setNetworkInfo Network changed: \(String(describing: info))")
DispatchQueue.main.sync {
ChatModel.shared.networkInfo = info
}
if !hasChatCtrl() { return }
self.monitorLock.sync {
do {
try apiSetNetworkInfo(info)
} catch let err {
logger.error("setNetworkInfo error: \(responseError(err))")
}
}
}
private func reconnectServers() {
Task {
if !hasChatCtrl() { return }
do {
try await reconnectAllServers()
} catch let err {
logger.error("reconnectAllServers error: \(responseError(err))")
}
}
}
}