Skip to content

Commit 6201b88

Browse files
committed
Sequence wip
1 parent 6c5161e commit 6201b88

2 files changed

Lines changed: 99 additions & 66 deletions

File tree

Sources/NetworkPathMonitor/NetworkPath.swift

Lines changed: 85 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ public struct NetworkPath: Sendable {
4646
public let rawNWPath: NWPath
4747

4848
/// An enum indicating the sequence of update.
49-
public var sequence: Sequence
49+
/// It only grow when the path update triggered.
50+
public private(set) var sequence: Sequence
5051

5152
public init(nwPath: NWPath, sequence: Sequence = .initial) {
5253
rawNWPath = nwPath
@@ -56,15 +57,17 @@ public struct NetworkPath: Sendable {
5657
supportsDNS = nwPath.supportsDNS
5758
isConstrained = nwPath.isConstrained
5859
isExpensive = nwPath.isExpensive
59-
self.sequence = sequence
6060
availableInterfaces = nwPath.availableInterfaces.compactMap { nwInterface in
6161
guard var interface = NetworkKit.Interface.interfaces(matching: { $0 == nwInterface.name }).first else { return nil }
6262
interface.associateNWInterface(nwInterface)
6363
return interface
6464
}
65+
self.sequence = sequence
6566
}
6667
}
6768

69+
// MARK: - Interface
70+
6871
public extension NetworkPath {
6972
/// Network interfaces used by this path
7073
var usedInterfaces: [NetworkKit.Interface] {
@@ -90,6 +93,8 @@ public extension NetworkPath {
9093
}
9194
}
9295

96+
// MARK: - Status
97+
9398
public extension NetworkPath {
9499
enum Status: Sendable, Equatable {
95100
/// The path has a usable route upon which to send and receive data
@@ -115,8 +120,12 @@ public extension NetworkPath {
115120
}
116121
}
117122
}
123+
}
118124

119-
@available(iOS 14.2, macCatalyst 14.2, macOS 11.0, tvOS 14.2, visionOS 1.0, watchOS 7.1, *)
125+
// MARK: - UnsatisfiedReason
126+
127+
@available(iOS 14.2, macCatalyst 14.2, macOS 11.0, tvOS 14.2, visionOS 1.0, watchOS 7.1, *)
128+
public extension NetworkPath {
120129
enum UnsatisfiedReason: Sendable, CustomStringConvertible {
121130
/// No reason is given
122131
case notAvailable
@@ -170,93 +179,111 @@ public extension NetworkPath {
170179
// MARK: - Sequence
171180

172181
public extension NetworkPath {
173-
indirect enum Sequence: Sendable, Equatable, CustomDebugStringConvertible {
174-
/// The initial path when the `NWPathMonitor` is created
175-
case initial
176-
182+
/// An enum representing the sequence of updates for a NetworkPath.
183+
/// Using an enum for indirect recursion: https://forums.swift.org/t/using-indirect-modifier-for-struct-properties/37600/14
184+
indirect enum Sequence: Sendable, Equatable, CustomStringConvertible, CustomDebugStringConvertible {
177185
/// An update triggered by the `pathUpdateHandler` closure of `NWPathMonitor`
178-
case update(_ index: UInt, _ previousPath: NetworkPath?)
179-
180-
/// Convenience initializer for creating a `Sequence.update`
181-
static func updating(_ previousPath: NetworkPath) -> Sequence {
182-
var path = previousPath
183-
// clear previous path avoid infinite reference
184-
path.sequence.previousPath = nil
185-
return .update(previousPath.sequence.nextIndex, path)
186-
}
186+
case index(_ index: Int, _ previousPath: NetworkPath?)
187+
188+
/// Convenience initializer for the initial sequence
189+
public static let initial = Sequence.index(0, nil)
187190

188191
/// The previous path in the sequence, if it exists
189192
var previousPath: NetworkPath? {
190-
get {
191-
switch self {
192-
case .initial:
193-
return nil
194-
case let .update(_, previousPath):
195-
return previousPath
196-
}
197-
}
198-
set {
199-
switch self {
200-
case let .update(index, _):
201-
self = .update(index, newValue)
202-
default:
203-
return
204-
}
193+
switch self {
194+
case let .index(_, previousPath): return previousPath
205195
}
206196
}
207197

208198
/// Indicates whether this is the initial path in the sequence
209-
var isInitial: Bool {
199+
var isInitial: Bool { index == 0 }
200+
201+
/// Indicates whether this is the first update in the sequence
202+
var isFirstUpdate: Bool { index == 1 }
203+
204+
/// The current index of this sequence update, if it exists
205+
var index: Int {
210206
switch self {
211-
case .initial:
212-
return true
213-
case .update:
214-
return false
207+
case let .index(value, _): return value
215208
}
216209
}
217210

218-
/// Indicates whether this is the first update in the sequence
219-
var isFirstUpdate: Bool {
211+
/// The next index in the sequence, which is one greater than the current index
212+
var nextIndex: Int { index + 1 }
213+
214+
public var description: String { debugDescription }
215+
216+
public var debugDescription: String {
220217
switch self {
221-
case .initial:
222-
return false
223-
case let .update(index, _):
224-
return index == 0
218+
case let .index(index, _): return "index(\(index))"
225219
}
226220
}
221+
}
227222

228-
/// The current index of this sequence update, if it exists
229-
var index: UInt? {
223+
/// Updates the sequence
224+
mutating func updateSequence(_ sequence: Sequence) {
225+
self.sequence = sequence
226+
}
227+
228+
/// Clears the previous path
229+
mutating func clearPreviousPath() {
230+
sequence = .index(sequence.index, nil)
231+
}
232+
}
233+
234+
public extension NetworkPath {
235+
enum UpdateReason: Sendable, Equatable {
236+
/// The path is the initial path when the `NWPathMonitor` is started
237+
case initial
238+
/// The path has changed due to a physical interface change
239+
case physicalChange
240+
/// The reason for the update is uncertain
241+
case uncertain
242+
243+
/// Indicates whether this is the initial path.
244+
var isInitial: Bool {
230245
switch self {
231246
case .initial:
232-
return nil
233-
case let .update(index, _):
234-
return index
247+
return true
248+
default:
249+
return false
235250
}
236251
}
237252

238-
/// The next index in the sequence, which is one greater than the current index
239-
var nextIndex: UInt {
240-
index.map { $0 + 1 } ?? 0
241-
}
242-
243-
public var debugDescription: String {
253+
/// Indicates whether this is a physical interface change.
254+
var isPhysicalChange: Bool {
244255
switch self {
245-
case .initial:
246-
return "initial"
247-
case let .update(index, _):
248-
return "update(index: \(index))"
256+
case .physicalChange:
257+
return true
258+
default:
259+
return false
249260
}
250261
}
251262
}
263+
264+
/// An enum indicating the reason of update.
265+
var updateReason: UpdateReason {
266+
if sequence.isInitial {
267+
// The initial path is created when the `NWPathMonitor` is started
268+
return .initial
269+
}
270+
let previousUsedPhysicalInterfaces = sequence.previousPath?.usedPhysicalInterfaces
271+
guard previousUsedPhysicalInterfaces == usedPhysicalInterfaces else {
272+
// If the used physical interfaces have changed, it indicates a physical interface change
273+
return .physicalChange
274+
}
275+
return .uncertain
276+
}
252277
}
253278

279+
// MARK: - Equatable & CustomStringConvertible
280+
254281
extension NetworkPath: Equatable, CustomStringConvertible, CustomDebugStringConvertible {
255282
public var description: String {
256283
"NetworkPath(status: \(status), availableInterfaces: \(availableInterfaces.map(\.name))"
257284
}
258285

259286
public var debugDescription: String {
260-
"NetworkPath(status: \(status), availableInterfaces: \(availableInterfaces.map(\.name)), supportsIPv4: \(supportsIPv4), supportsIPv6: \(supportsIPv6), supportsDNS: \(supportsDNS), isConstrained: \(isConstrained), isExpensive: \(isExpensive)), sequence: \(sequence.debugDescription), isSatisfied: \(isSatisfied))"
287+
"NetworkPath(status: \(status), availableInterfaces: \(availableInterfaces.map(\.name)), supportsIPv4: \(supportsIPv4), supportsIPv6: \(supportsIPv6), supportsDNS: \(supportsDNS), isConstrained: \(isConstrained), isExpensive: \(isExpensive)), isSatisfied: \(isSatisfied)), sequence: \(sequence.debugDescription), previousSequence: \(sequence.previousPath?.sequence.debugDescription ?? "null")"
261288
}
262289
}

Sources/NetworkPathMonitor/NetworkPathMonitor.swift

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -93,21 +93,24 @@ public actor NetworkPathMonitor {
9393
}
9494

9595
private func handlePathUpdate(_ path: NWPath) async {
96-
let networkPath = NetworkPath(nwPath: path, sequence: .updating(currentPath))
97-
currentPath = networkPath
96+
var previousPath = currentPath
97+
// Before yielding, keep the previous sequence
98+
currentPath = NetworkPath(nwPath: path, sequence: previousPath.sequence)
99+
// clear previous path avoid infinite reference
100+
previousPath.clearPreviousPath()
98101

99102
debounceTask?.cancel()
100103
guard debounceInterval.nanoseconds > 0 else {
101104
// No debounce, yield immediately
102105
debounceTask = nil
103-
await yieldNetworkPath(networkPath)
106+
await yieldNetworkPath(previousPath: previousPath)
104107
return
105108
}
106109
// Debounce is active
107110
debounceTask = Task {
108111
do {
109112
try await Task.sleep(nanoseconds: UInt64(self.debounceInterval.nanoseconds))
110-
await self.yieldNetworkPath(networkPath)
113+
await self.yieldNetworkPath(previousPath: previousPath)
111114
} catch is CancellationError {
112115
// Task was cancelled, do nothing
113116
} catch {
@@ -117,20 +120,23 @@ public actor NetworkPathMonitor {
117120
}
118121

119122
// Yield the path update handler
120-
private func yieldNetworkPath(_ path: NetworkPath) async {
123+
private func yieldNetworkPath(previousPath: NetworkPath) async {
124+
// Update sequence
125+
currentPath.updateSequence(.index(previousPath.sequence.nextIndex, previousPath))
126+
121127
// Send updates via AsyncStream
122-
pathUpdateContinuation?.yield(path)
128+
pathUpdateContinuation?.yield(currentPath)
123129

124130
// Send updates via handler
125-
Task { await self.networkPathUpdater?(path) }
131+
Task { await self.networkPathUpdater?(currentPath) }
126132

127133
// Post network status change notification
128134
Task {
129135
NotificationCenter.default.post(
130136
name: Self.networkStatusDidChangeNotification,
131137
object: self,
132138
userInfo: [
133-
"newPath": path,
139+
"newPath": currentPath,
134140
]
135141
)
136142
}

0 commit comments

Comments
 (0)