@@ -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+
6871public 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+
9398public 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
172181public 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+
254281extension 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}
0 commit comments