Skip to content

Commit 610b3e1

Browse files
committed
Fix six Swift 6 strict-concurrency errors
- PresentationManager: screen-change observer asserts MainActor (queue: .main) - BibleView / MainControlView: dropped-URL collection behind OSAllocatedUnfairLock instead of mutating a captured var across provider callback threads - PreviewPanelView: ForceTouch view teardown via isolated deinit (SE-0371) - WindowNotifications: PresentationCommandRouter handlers typed @mainactor - TextBoxLayout: transitionRow get/set typed @mainactor; pass { get() } to Binding — the direct function value makes swift-frontend crash emitting an @isolated(any) reabstraction thunk (Xcode 26.2 IRGen SmallVector abort)
1 parent 812c15b commit 610b3e1

6 files changed

Lines changed: 27 additions & 16 deletions

File tree

TopPresenter/Core/PresentationManager.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -927,7 +927,11 @@ final class PresentationManager {
927927
object: nil,
928928
queue: .main
929929
) { [weak self] _ in
930-
self?.handleScreenConfigurationChange()
930+
// queue: .main guarantees main-thread delivery; the closure is just
931+
// typed nonisolated @Sendable, so assert the isolation for Swift 6.
932+
MainActor.assumeIsolated {
933+
self?.handleScreenConfigurationChange()
934+
}
931935
}
932936
}
933937

TopPresenter/Views/Bible/BibleView.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import SwiftUI
99
import SwiftData
1010
import UniformTypeIdentifiers
11+
import os
1112

1213
/// Main Bible view with module selection, book/chapter navigation, search, and verse display.
1314
struct BibleView: View {
@@ -1388,16 +1389,16 @@ struct BibleImportSheet: View {
13881389
/// Load dropped file URLs asynchronously, then route them.
13891390
private func loadDroppedURLs(_ providers: [NSItemProvider]) {
13901391
let group = DispatchGroup()
1391-
let lock = NSLock()
1392-
var urls: [URL] = []
1392+
// Provider callbacks arrive on arbitrary threads — collect behind a lock.
1393+
let collected = OSAllocatedUnfairLock(initialState: [URL]())
13931394
for p in providers where p.canLoadObject(ofClass: URL.self) {
13941395
group.enter()
13951396
_ = p.loadObject(ofClass: URL.self) { url, _ in
1396-
if let url { lock.lock(); urls.append(url); lock.unlock() }
1397+
if let url { collected.withLock { $0.append(url) } }
13971398
group.leave()
13981399
}
13991400
}
1400-
group.notify(queue: .main) { handleSelectedURLs(urls) }
1401+
group.notify(queue: .main) { handleSelectedURLs(collected.withLock { $0 }) }
14011402
}
14021403

14031404
/// One file → inline single-import flow (detect/override format). Multiple

TopPresenter/Views/Main/MainControlView.swift

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import SwiftUI
99
import SwiftData
1010
import UniformTypeIdentifiers
11+
import os
1112

1213
/// The main control window - split into sidebar, content area, and preview panel.
1314
struct MainControlView: View {
@@ -265,9 +266,8 @@ struct MainControlView: View {
265266
}
266267

267268
private func handleDrop(providers: [NSItemProvider]) {
268-
// Provider callbacks arrive on arbitrary threads — serialize the appends.
269-
let lock = NSLock()
270-
var urls: [URL] = []
269+
// Provider callbacks arrive on arbitrary threads — collect behind a lock.
270+
let collected = OSAllocatedUnfairLock(initialState: [URL]())
271271
let group = DispatchGroup()
272272

273273
for provider in providers {
@@ -277,14 +277,13 @@ struct MainControlView: View {
277277
if let data = data as? Data,
278278
let urlString = String(data: data, encoding: .utf8),
279279
let url = URL(string: urlString) {
280-
lock.lock()
281-
urls.append(url)
282-
lock.unlock()
280+
collected.withLock { $0.append(url) }
283281
}
284282
}
285283
}
286284

287285
group.notify(queue: .main) { [self] in
286+
let urls = collected.withLock { $0 }
288287
guard !urls.isEmpty else { return }
289288
Task {
290289
// EXPAND folders first (max 2 subfolder levels, USFM kept whole) —

TopPresenter/Views/Main/PreviewPanelView.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1262,7 +1262,9 @@ struct ForceTouchDetector: NSViewRepresentable {
12621262
return nil
12631263
}
12641264

1265-
deinit {
1265+
// isolated deinit (SE-0371): runs on the main actor — NSEvent monitor
1266+
// teardown touches NSView-isolated state.
1267+
isolated deinit {
12661268
removeMonitor()
12671269
}
12681270
}

TopPresenter/Views/Main/WindowNotifications.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,11 @@ final class PresentationCommandRouter {
9797

9898
init(pm: PresentationManager) {
9999
let center = NotificationCenter.default
100-
func on(_ name: Notification.Name, _ handler: @escaping () -> Void) {
100+
func on(_ name: Notification.Name, _ handler: @escaping @MainActor () -> Void) {
101101
tokens.append(center.addObserver(forName: name, object: nil, queue: .main) { _ in
102-
handler()
102+
// queue: .main guarantees main-thread delivery; the observer
103+
// closure is typed nonisolated @Sendable, so assert isolation.
104+
MainActor.assumeIsolated(handler)
103105
})
104106
}
105107

TopPresenter/Views/Presentation/TextBoxLayout.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3600,11 +3600,14 @@ struct LayoutEditorSheet: View {
36003600
@ViewBuilder
36013601
private func transitionRow(
36023602
label: String, help: String,
3603-
get: @escaping () -> String, set: @escaping (String) -> Void
3603+
get: @escaping @MainActor () -> String, set: @escaping @MainActor (String) -> Void
36043604
) -> some View {
36053605
labeledRow(label) {
3606+
// `{ get() }` not `get` — passing the function value directly makes the
3607+
// compiler emit an @isolated(any) reabstraction thunk that crashes
3608+
// swift-frontend (Xcode 26.2 IRGen, SmallVector capacity abort).
36063609
Picker("", selection: Binding(
3607-
get: get,
3610+
get: { get() },
36083611
set: { raw in
36093612
set(raw)
36103613
playTransitionPreview(raw)

0 commit comments

Comments
 (0)