Skip to content

Commit 54b2bac

Browse files
committed
Demo: network monitor added
1 parent bf5495b commit 54b2bac

File tree

4 files changed

+75
-25
lines changed

4 files changed

+75
-25
lines changed

AsyncMuxDemo/AsyncMuxDemo.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
361F7D152C32A0750050F657 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 361F7D142C32A0750050F657 /* ImageCache.swift */; };
1414
361F7D182C32A0EA0050F657 /* LRUCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 361F7D172C32A0EA0050F657 /* LRUCache.swift */; };
1515
3625CE1E296A283800D09A8F /* Globals.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3625CE1D296A283800D09A8F /* Globals.swift */; };
16+
362906AE2E0BE68C00B94290 /* NetworkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 362906AD2E0BE68C00B94290 /* NetworkMonitor.swift */; };
1617
36C131B829805B8E0076E476 /* AsyncMux.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 36C131B729805B8E0076E476 /* AsyncMux.framework */; };
1718
36C131B929805B8E0076E476 /* AsyncMux.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 36C131B729805B8E0076E476 /* AsyncMux.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
1819
36FB7010296A10F90006ACBA /* AsyncMuxDemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36FB700F296A10F90006ACBA /* AsyncMuxDemoApp.swift */; };
@@ -42,6 +43,7 @@
4243
361F7D142C32A0750050F657 /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = "<group>"; };
4344
361F7D172C32A0EA0050F657 /* LRUCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LRUCache.swift; sourceTree = "<group>"; };
4445
3625CE1D296A283800D09A8F /* Globals.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Globals.swift; sourceTree = "<group>"; };
46+
362906AD2E0BE68C00B94290 /* NetworkMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitor.swift; sourceTree = "<group>"; };
4547
368523012C391B31006D6846 /* AsyncMuxDemo.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = AsyncMuxDemo.entitlements; sourceTree = "<group>"; };
4648
368523022C391B31006D6846 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
4749
36C131B729805B8E0076E476 /* AsyncMux.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AsyncMux.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -108,6 +110,7 @@
108110
children = (
109111
361F7D162C32A09C0050F657 /* RemoteImage */,
110112
3625CE1D296A283800D09A8F /* Globals.swift */,
113+
362906AD2E0BE68C00B94290 /* NetworkMonitor.swift */,
111114
36FB701F296A14400006ACBA /* AppError.swift */,
112115
36FB701D296A12D20006ACBA /* URLRequestEx.swift */,
113116
360EBA4D296A2C10001EC729 /* WeatherAPI.swift */,
@@ -195,6 +198,7 @@
195198
36FB7012296A10F90006ACBA /* ContentView.swift in Sources */,
196199
360EBA56296A5388001EC729 /* ErrorAlert.swift in Sources */,
197200
36FB701E296A12D20006ACBA /* URLRequestEx.swift in Sources */,
201+
362906AE2E0BE68C00B94290 /* NetworkMonitor.swift in Sources */,
198202
361F7D152C32A0750050F657 /* ImageCache.swift in Sources */,
199203
360EBA4E296A2C10001EC729 /* WeatherAPI.swift in Sources */,
200204
361F7D182C32A0EA0050F657 /* LRUCache.swift in Sources */,

AsyncMuxDemo/Sources/AsyncMuxDemoApp.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ import AsyncMux
1212
struct AsyncMuxDemoApp: App {
1313
@Environment(\.scenePhase) var scenePhase
1414

15+
init() {
16+
NetworkMonitor.start()
17+
}
18+
1519
var body: some Scene {
1620
scene()
1721
.onChange(of: scenePhase) { _, newPhase in

AsyncMuxDemo/Sources/ContentView.swift

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -29,29 +29,30 @@ struct ContentView: View {
2929

3030
.task {
3131
guard !Globals.isPreview else { return }
32-
do {
33-
try await reload()
34-
}
35-
catch {
36-
self.error = error
37-
}
32+
await reload()
3833
}
3934

4035
.refreshable {
4136
guard !Globals.isPreview else { return }
42-
do {
43-
await WeatherAPI.map.refresh()
44-
try await reload()
45-
}
46-
catch {
47-
self.error = error
48-
}
37+
await WeatherAPI.map.refresh()
38+
await reload()
4939
}
5040

5141
.errorAlert($error)
5242

43+
// Handle internet connection recovery by refreshing the data; this is also called at app startup
44+
.onReceive(NotificationCenter.default.publisher(for: .networkDidChangeStatus)) { output in
45+
if let connected = output.object as? Bool, connected {
46+
print("Connection is back...")
47+
Task {
48+
await WeatherAPI.map.refresh()
49+
await reload()
50+
}
51+
}
52+
}
53+
5354
// Purge memory caches on memory warnings from the OS
54-
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didReceiveMemoryWarningNotification, object: nil)) { _ in
55+
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didReceiveMemoryWarningNotification)) { _ in
5556
Task {
5657
await MuxRepository.clearMemory()
5758
ImageCache.clear()
@@ -98,19 +99,24 @@ struct ContentView: View {
9899
}
99100

100101
@MainActor
101-
private func reload() async throws {
102-
// Create an array of actions for the zipper
103-
let actions = placeNames.map { name in
104-
{ @Sendable in
105-
try await WeatherAPI.map.request(key: name)
102+
private func reload() async {
103+
do {
104+
// Create an array of actions for the zipper
105+
let actions = placeNames.map { name in
106+
{ @Sendable in
107+
try await WeatherAPI.map.request(key: name)
108+
}
106109
}
110+
// Execute the array of actions in parallel, get the results in an array and convert them to a dictionary to be used in the UI
111+
weather = try await Zip(actions: actions)
112+
.result
113+
.reduce(into: [String: WeatherItem]()) {
114+
$0[$1.name] = $1
115+
}
116+
}
117+
catch {
118+
self.error = error
107119
}
108-
// Execute the array of actions in parallel, get the results in an array and convert them to a dictionary to be used in the UI
109-
weather = try await Zip(actions: actions)
110-
.result
111-
.reduce(into: [String: WeatherItem]()) {
112-
$0[$1.name] = $1
113-
}
114120
}
115121
}
116122

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//
2+
// NetworkMonitor.swift
3+
// AsyncMuxDemo
4+
//
5+
// Created by Hovik Melikyan on 25.06.25.
6+
//
7+
8+
import Foundation
9+
import Network
10+
11+
12+
final class NetworkMonitor {
13+
14+
static func start() {
15+
Task { @MainActor in
16+
for await path in monitor {
17+
print("Connection is:", path.debugDescription)
18+
// NWPath sends a lot of strange and even irrelevant statuses; the only way to make sense of it is to compare to the previous known status, and also to assume `.satisfied` at program startup so that the first event is not fired, as it's not necessary.
19+
if path.status != lastStatus {
20+
lastStatus = path.status
21+
NotificationCenter.default.post(name: .networkDidChangeStatus, object: path.status == .satisfied)
22+
}
23+
}
24+
}
25+
}
26+
27+
@MainActor
28+
private static var lastStatus: NWPath.Status = .satisfied
29+
30+
private static let monitor = NWPathMonitor()
31+
}
32+
33+
34+
extension Notification.Name {
35+
static let networkDidChangeStatus = Self("networkDidChangeStatus") // arg = Bool
36+
}

0 commit comments

Comments
 (0)