Skip to content

Commit ea539f4

Browse files
fix(build): clear remaining Xcode 15.4 concurrency + SDK compile errors
Continue fixing latent errors that only surface now that Build & Test runs: - Timer / callback closures that captured the outer weak 'self' var inside a concurrently-executing Task { @mainactor } now give the Task its own [weak self] capture list (game views, MusicKit, MeshOutbox, HeartbeatWatchdog, WatchPortfolioView, ReplayKit, GameKit, ContactsImport, GameRunner). - TrinitySiri.isOnline() captured/mutated a local 'var resumed' inside the @sendable NWPathMonitor handler; moved the one-shot latch into a lock-guarded @unchecked Sendable box. - NetworkPathMonitor referenced NWPath.isUltraConstrained (an iOS 26+ symbol absent from the Xcode 15.4 SDK) behind only a runtime #available; a runtime check does not make the symbol exist at compile time, so it is now also behind #if compiler(>=6.2). Behavior is unchanged on shipping OS versions. All changes are behavior-preserving; they satisfy the Swift 5.10 concurrency checker and the pinned SDK. Co-authored-by: Dardan <ItsDardanRexhepi@users.noreply.github.com>
1 parent 4985f23 commit ea539f4

14 files changed

Lines changed: 42 additions & 24 deletions

Apple/AppIntents/TrinitySiri.swift

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -82,17 +82,29 @@ final class TrinitySiriSession {
8282
/// One-shot reachability check; NWPathMonitor reports the current
8383
/// path immediately on start.
8484
static func isOnline() async -> Bool {
85-
await withCheckedContinuation { continuation in
86-
let monitor = NWPathMonitor()
87-
let lock = NSLock()
88-
var resumed = false
89-
monitor.pathUpdateHandler = { path in
85+
// One-shot latch shared with the @Sendable pathUpdateHandler. A plain
86+
// local `var` can't be captured/mutated by a @Sendable closure (hard
87+
// error under the CI toolchain), so the flag lives behind a lock in an
88+
// @unchecked Sendable box.
89+
final class OneShot: @unchecked Sendable {
90+
private let lock = NSLock()
91+
private var fired = false
92+
func runOnce(_ body: () -> Void) {
9093
lock.lock()
9194
defer { lock.unlock() }
92-
guard !resumed else { return }
93-
resumed = true
94-
continuation.resume(returning: path.status == .satisfied)
95-
monitor.cancel()
95+
guard !fired else { return }
96+
fired = true
97+
body()
98+
}
99+
}
100+
return await withCheckedContinuation { continuation in
101+
let monitor = NWPathMonitor()
102+
let gate = OneShot()
103+
monitor.pathUpdateHandler = { path in
104+
gate.runOnce {
105+
continuation.resume(returning: path.status == .satisfied)
106+
monitor.cancel()
107+
}
96108
}
97109
monitor.start(queue: DispatchQueue(label: "com.mtrx.trinity.netcheck"))
98110
}

Apple/Gaming/GameKitManager.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ final class GameKitManager {
6666
guard authState == .unknown || authState == .unavailable else { return }
6767
authState = .authenticating
6868
GKLocalPlayer.local.authenticateHandler = { [weak self] viewController, _ in
69-
Task { @MainActor in
69+
Task { @MainActor [weak self] in
7070
guard let self else { return }
7171
if let viewController {
7272
Self.present(viewController)

Apple/Media/ReplayKitManager.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ final class ReplayKitManager {
192192
private func startTicker() {
193193
ticker?.invalidate()
194194
ticker = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] _ in
195-
Task { @MainActor in
195+
Task { @MainActor [weak self] in
196196
guard let self, let started = self.startedAt else { return }
197197
self.elapsed = Date().timeIntervalSince(started)
198198
}

Apple/Music/MusicKitManager.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ final class MusicKitManager {
266266
endObserver = NotificationCenter.default.addObserver(
267267
forName: .AVPlayerItemDidPlayToEndTime, object: item, queue: .main
268268
) { [weak self] _ in
269-
Task { @MainActor in self?.previewEnded() }
269+
Task { @MainActor [weak self] in self?.previewEnded() }
270270
}
271271
p.play()
272272
isPreviewPlayback = true
@@ -409,7 +409,7 @@ final class MusicKitManager {
409409
private func startTicker() {
410410
stopTicker()
411411
ticker = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] _ in
412-
Task { @MainActor in self?.tick() }
412+
Task { @MainActor [weak self] in self?.tick() }
413413
}
414414
}
415415
private func stopTicker() { ticker?.invalidate(); ticker = nil }

Apple/WatchKit/WatchPortfolioView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ final class WatchPortfolioViewModel: ObservableObject {
6464
}
6565
guard WCSession.default.isReachable else { return }
6666
WCSession.default.sendMessage(["request": "portfolio"], replyHandler: { [weak self] reply in
67-
Task { @MainActor in self?.updateFrom(reply) }
67+
Task { @MainActor [weak self] in self?.updateFrom(reply) }
6868
}, errorHandler: nil)
6969
}
7070

Core/Mesh/MeshOutbox.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ final class MeshOutbox: ObservableObject {
8181
private func startDrivingIfNeeded() {
8282
guard driveTimer == nil else { return }
8383
driveTimer = Timer.scheduledTimer(withTimeInterval: 0.6, repeats: true) { [weak self] _ in
84-
Task { @MainActor in self?.tick() }
84+
Task { @MainActor [weak self] in self?.tick() }
8585
}
8686
}
8787

Core/Network/NetworkPathMonitor.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,16 @@ final class NetworkPathMonitor: ObservableObject {
4646
// Compute everything off the main thread; publish on main.
4747
let satisfied = path.status == .satisfied
4848
let constrained = path.isConstrained // Low Data Mode
49+
// `NWPath.isUltraConstrained` is an iOS 26+ symbol that does not
50+
// exist in the CI SDK (Xcode 15.4 / iOS 17), so referencing it fails
51+
// to compile even behind a runtime #available check. Compile it only
52+
// when the SDK actually declares it; otherwise treat as not-ultra.
4953
var ultra = false
54+
#if compiler(>=6.2)
5055
if #available(iOS 26.0, *) {
5156
ultra = path.isUltraConstrained // carrier ultra-constrained
5257
}
58+
#endif
5359
Task { @MainActor [weak self] in
5460
self?.apply(satisfied: satisfied, constrained: constrained, ultra: ultra)
5561
}

Core/Security/HeartbeatWatchdog.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ final class HeartbeatWatchdog: ObservableObject {
3737
if stored == 0 { persist() }
3838
// Light minute-resolution ticker for the countdown UI.
3939
ticker = Timer.scheduledTimer(withTimeInterval: 30, repeats: true) { [weak self] _ in
40-
Task { @MainActor in self?.refresh() }
40+
Task { @MainActor [weak self] in self?.refresh() }
4141
}
4242
}
4343

UI/Views/Gaming/AsteroidStormView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ final class AsteroidStormEngine: ObservableObject {
111111
private func startTimer() {
112112
timer?.invalidate()
113113
timer = Timer.scheduledTimer(withTimeInterval: 1.0 / 120.0, repeats: true) { [weak self] _ in
114-
Task { @MainActor in self?.tick() }
114+
Task { @MainActor [weak self] in self?.tick() }
115115
}
116116
}
117117
func stop() { timer?.invalidate(); timer = nil }

UI/Views/Gaming/BlockGameView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ final class BlockEngine: ObservableObject {
174174
private func restartTimer() {
175175
timer?.invalidate()
176176
timer = Timer.scheduledTimer(withTimeInterval: gravity, repeats: true) { [weak self] _ in
177-
Task { @MainActor in self?.tick() }
177+
Task { @MainActor [weak self] in self?.tick() }
178178
}
179179
}
180180

0 commit comments

Comments
 (0)