Skip to content

Commit 94658ea

Browse files
committed
NetworkPath wip
1 parent 4be475e commit 94658ea

File tree

6 files changed

+105
-145
lines changed

6 files changed

+105
-145
lines changed

Package.resolved

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ let package = Package(
2121
),
2222
],
2323
dependencies: [
24-
.package(url: "https://github.com/codingiran/NetworkKit.git", .upToNextMajor(from: "0.2.5")),
24+
.package(url: "https://github.com/codingiran/NetworkKit.git", .upToNextMajor(from: "0.2.6")),
2525
],
2626
targets: [
2727
// Targets are the basic building blocks of a package, defining a module or a test suite.

[email protected]

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ let package = Package(
2121
),
2222
],
2323
dependencies: [
24-
.package(url: "https://github.com/codingiran/NetworkKit.git", .upToNextMajor(from: "0.2.5")),
24+
.package(url: "https://github.com/codingiran/NetworkKit.git", .upToNextMajor(from: "0.2.6")),
2525
],
2626
targets: [
2727
// Targets are the basic building blocks of a package, defining a module or a test suite.

Sources/NetworkPathMonitor/NetworkPath.swift

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,24 +36,29 @@ public struct NetworkPath: Sendable {
3636
/// A Boolean indicating whether the path uses an interface that is considered expensive, such as Cellular or a Personal Hotspot.
3737
public let isExpensive: Bool
3838

39+
/// A Boolean indicating whether the path is satisfied.
40+
public var isSatisfied: Bool { status == .satisfied }
41+
3942
@available(iOS 14.2, macCatalyst 14.2, macOS 11.0, tvOS 14.2, visionOS 1.0, watchOS 7.1, *)
4043
public var unsatisfiedReason: UnsatisfiedReason? { .init(nwUnsatisfiedReason: rawNWPath.unsatisfiedReason) }
4144

4245
/// The raw NWPath instance that this NetworkPath wraps.
4346
public let rawNWPath: NWPath
4447

45-
public init(nwPath: NWPath) {
48+
/// An enum indicating the sequence of update.
49+
public var sequence: Sequence
50+
51+
public init(nwPath: NWPath, sequence: Sequence = .initial) {
4652
rawNWPath = nwPath
4753
status = Status(nwStatus: nwPath.status)
4854
supportsIPv4 = nwPath.supportsIPv4
4955
supportsIPv6 = nwPath.supportsIPv6
5056
supportsDNS = nwPath.supportsDNS
5157
isConstrained = nwPath.isConstrained
5258
isExpensive = nwPath.isExpensive
53-
54-
let allInterfaces = NetworkKit.Interface.allInterfaces()
59+
self.sequence = sequence
5560
availableInterfaces = nwPath.availableInterfaces.compactMap { nwInterface in
56-
guard var interface = allInterfaces.first(where: { $0.name == nwInterface.name }) else { return nil }
61+
guard var interface = NetworkKit.Interface.interfaces(matching: { $0 == nwInterface.name }).first else { return nil }
5762
interface.associateNWInterface(nwInterface)
5863
return interface
5964
}
@@ -86,7 +91,7 @@ public extension NetworkPath {
8691
}
8792

8893
public extension NetworkPath {
89-
enum Status: Sendable {
94+
enum Status: Sendable, Equatable {
9095
/// The path has a usable route upon which to send and receive data
9196
case satisfied
9297

@@ -160,6 +165,69 @@ public extension NetworkPath {
160165
}
161166
}
162167
}
168+
169+
indirect enum Sequence: Sendable, Equatable {
170+
/// The initial path when the `NWPathMonitor` is created
171+
case initial
172+
173+
/// An update triggered by the `pathUpdateHandler` closure of `NWPathMonitor`
174+
case update(_ index: UInt, _ previousPath: NetworkPath?)
175+
176+
var previousPath: NetworkPath? {
177+
get {
178+
switch self {
179+
case .initial:
180+
return nil
181+
case let .update(_, previousPath):
182+
return previousPath
183+
}
184+
}
185+
set {
186+
switch self {
187+
case let .update(index, _):
188+
self = .update(index, newValue)
189+
default:
190+
return
191+
}
192+
}
193+
}
194+
195+
var isInitial: Bool {
196+
switch self {
197+
case .initial:
198+
return true
199+
case .update:
200+
return false
201+
}
202+
}
203+
204+
var isFirstUpdate: Bool {
205+
switch self {
206+
case .initial:
207+
return false
208+
case let .update(index, _):
209+
return index == 0
210+
}
211+
}
212+
213+
var index: UInt? {
214+
switch self {
215+
case .initial:
216+
return nil
217+
case let .update(index, _):
218+
return index
219+
}
220+
}
221+
222+
var nextIndex: UInt {
223+
switch self {
224+
case .initial:
225+
return 0
226+
case let .update(index, _):
227+
return index + 1
228+
}
229+
}
230+
}
163231
}
164232

165233
extension NetworkPath: Equatable, CustomDebugStringConvertible {

Sources/NetworkPathMonitor/NetworkPathMonitor.swift

Lines changed: 13 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import Network
1616

1717
public enum NetworkPathMonitorInfo: Sendable {
1818
/// Current NetworkPathMonitor version.
19-
public static let version = "0.0.7"
19+
public static let version = "0.2.0"
2020
}
2121

2222
/// A class that monitors network path changes using `NWPathMonitor`.
@@ -29,9 +29,6 @@ public actor NetworkPathMonitor {
2929
/// Debounce interval in seconds.
3030
private let debounceInterval: Interval
3131

32-
/// Ignore first path update.
33-
private let ignoreFirstPathUpdate: Bool
34-
3532
/// The network path update handler.
3633
private var networkPathUpdater: PathUpdateHandler?
3734

@@ -45,27 +42,21 @@ public actor NetworkPathMonitor {
4542
private var debounceTask: Task<Void, Never>?
4643

4744
/// Current network path.
48-
public private(set) var currentPath: NWPath
49-
50-
/// Flag to track if this is the first path update.
51-
private var isFirstUpdate: Bool = true
45+
public private(set) var currentPath: NetworkPath
5246

5347
/// Network path status change notification.
5448
public static let networkStatusDidChangeNotification = Notification.Name("NetworkPathMonitor.NetworkPathStatusDidChange")
5549

5650
/// Initializes a new instance of `NetworkPathMonitor`.
5751
/// - Parameter queue: The queue on which the network path monitor runs. Default is a serial queue with a unique label.
5852
/// - Parameter debounceInterval: Debounce interval. If set to 0, no debounce will be applied. Default is 0 seconds.
59-
/// - Parameter ignoreFirstPathUpdate: Ignore first path update. Default is false.
6053
public init(queue: DispatchQueue = .init(label: "com.networkPathMonitor.\(UUID())"),
61-
debounceInterval: Interval = .seconds(0),
62-
ignoreFirstPathUpdate: Bool = false)
54+
debounceInterval: Interval = .seconds(0))
6355
{
6456
precondition(debounceInterval.nanoseconds >= 0, "debounceInterval must be greater than or equal to 0")
6557
monitorQueue = queue
66-
currentPath = networkMonitor.currentPath
58+
currentPath = NetworkPath(nwPath: networkMonitor.currentPath)
6759
self.debounceInterval = debounceInterval
68-
self.ignoreFirstPathUpdate = ignoreFirstPathUpdate
6960
networkMonitor.pathUpdateHandler = { [weak self] path in
7061
guard let self else { return }
7162
Task { await self.handlePathUpdate(path) }
@@ -84,10 +75,10 @@ public actor NetworkPathMonitor {
8475
}
8576

8677
/// Updates the current network path and notifies the handler.
87-
private var pathUpdateContinuation: AsyncStream<NWPath>.Continuation?
78+
private var pathUpdateContinuation: AsyncStream<NetworkPath>.Continuation?
8879

8980
/// An asynchronous stream of network path updates.
90-
public var pathUpdates: AsyncStream<NWPath> {
81+
public var pathUpdates: AsyncStream<NetworkPath> {
9182
AsyncStream { continuation in
9283
self.pathUpdateContinuation = continuation
9384
// When the AsyncStream is cancelled, clean up the continuation
@@ -102,26 +93,22 @@ public actor NetworkPathMonitor {
10293
}
10394

10495
private func handlePathUpdate(_ path: NWPath) async {
105-
currentPath = path
106-
107-
// Check if we should ignore the first path update
108-
if isFirstUpdate, ignoreFirstPathUpdate {
109-
isFirstUpdate = false
110-
return
111-
}
96+
currentPath.sequence.previousPath = nil // clear previous path avoid infinite reference
97+
let networkPath = NetworkPath(nwPath: path, sequence: .update(currentPath.sequence.nextIndex, currentPath))
98+
currentPath = networkPath
11299

113100
debounceTask?.cancel()
114101
guard debounceInterval.nanoseconds > 0 else {
115102
// No debounce, yield immediately
116103
debounceTask = nil
117-
await yieldNetworkPath(path)
104+
await yieldNetworkPath(networkPath)
118105
return
119106
}
120107
// Debounce is active
121108
debounceTask = Task {
122109
do {
123110
try await Task.sleep(nanoseconds: UInt64(self.debounceInterval.nanoseconds))
124-
await self.yieldNetworkPath(path)
111+
await self.yieldNetworkPath(networkPath)
125112
} catch is CancellationError {
126113
// Task was cancelled, do nothing
127114
} catch {
@@ -131,10 +118,7 @@ public actor NetworkPathMonitor {
131118
}
132119

133120
// Yield the path update handler
134-
private func yieldNetworkPath(_ path: NWPath) async {
135-
// Mark first update as completed when actually notifying
136-
isFirstUpdate = false
137-
121+
private func yieldNetworkPath(_ path: NetworkPath) async {
138122
// Send updates via AsyncStream
139123
pathUpdateContinuation?.yield(path)
140124

@@ -158,7 +142,7 @@ public actor NetworkPathMonitor {
158142

159143
public extension NetworkPathMonitor {
160144
/// A type alias for the network path update handler.
161-
typealias PathUpdateHandler = @Sendable (Network.NWPath) async -> Void
145+
typealias PathUpdateHandler = @Sendable (NetworkPath) async -> Void
162146

163147
/// Starts monitoring the network path.
164148
func fire() {

0 commit comments

Comments
 (0)