From a06708a01fce70a56583b2f02c6e44cb757e7e0a Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Thu, 28 Aug 2025 13:34:05 -0500 Subject: [PATCH 1/2] Build Without Warnings On Swift 6.2 --- .../NSDocumentController+Extensions.swift | 54 ++++++++++--------- .../WelcomeWindow/Model/RecentsStore.swift | 2 +- .../Utils/DispatchQueue+asyncIfNot.swift | 22 ++++++++ .../WelcomeWindow/Views/WelcomeWindow.swift | 10 ++-- .../Views/WelcomeWindowView.swift | 34 ++++++++---- 5 files changed, 82 insertions(+), 40 deletions(-) create mode 100644 Sources/WelcomeWindow/Utils/DispatchQueue+asyncIfNot.swift diff --git a/Sources/WelcomeWindow/Model/NSDocumentController+Extensions.swift b/Sources/WelcomeWindow/Model/NSDocumentController+Extensions.swift index 3802862d4..3f20cafdd 100644 --- a/Sources/WelcomeWindow/Model/NSDocumentController+Extensions.swift +++ b/Sources/WelcomeWindow/Model/NSDocumentController+Extensions.swift @@ -25,7 +25,7 @@ extension NSDocumentController { public func createFileDocumentWithDialog( configuration: DocumentSaveDialogConfiguration = .init(), onDialogPresented: @escaping () -> Void = {}, - onCompletion: @escaping () -> Void = {}, + onCompletion: @MainActor @escaping () -> Void = {}, onCancel: @escaping () -> Void = {} ) { _createDocument( @@ -53,7 +53,7 @@ extension NSDocumentController { public func createFolderDocumentWithDialog( configuration: DocumentSaveDialogConfiguration, onDialogPresented: @escaping () -> Void = {}, - onCompletion: @escaping () -> Void = {}, + onCompletion: @MainActor @escaping () -> Void = {}, onCancel: @escaping () -> Void = {} ) { _createDocument( @@ -100,7 +100,7 @@ extension NSDocumentController { mode: SaveMode, configuration: DocumentSaveDialogConfiguration, onDialogPresented: @escaping () -> Void, - onCompletion: @escaping () -> Void, + onCompletion: @MainActor @escaping () -> Void, onCancel: @escaping () -> Void ) { // 1 ──────────────────────────────────────────────────────────────── @@ -177,8 +177,8 @@ extension NSDocumentController { public func openDocumentWithDialog( configuration: DocumentOpenDialogConfiguration = DocumentOpenDialogConfiguration(), onDialogPresented: @escaping () -> Void = {}, - onCompletion: @escaping () -> Void = {}, - onCancel: @escaping () -> Void = {} + onCompletion: @MainActor @escaping () -> Void = {}, + onCancel: @MainActor @escaping () -> Void = {} ) { let panel = NSOpenPanel() panel.title = configuration.title @@ -189,12 +189,14 @@ extension NSDocumentController { panel.level = .modalPanel panel.begin { result in - guard result == .OK, let selectedURL = panel.url else { - onCancel() - return - } + DispatchQueue.mainIfNot { + guard result == .OK, let selectedURL = panel.url else { + onCancel() + return + } - self.openDocument(at: selectedURL, onCompletion: onCompletion, onError: { _ in onCancel() }) + self.openDocument(at: selectedURL, onCompletion: onCompletion, onError: { _ in onCancel() }) + } } onDialogPresented() } @@ -208,25 +210,27 @@ extension NSDocumentController { @MainActor public func openDocument( at url: URL, - onCompletion: @escaping () -> Void = {}, - onError: @escaping (Error) -> Void = { _ in } + onCompletion: @MainActor @escaping () -> Void = {}, + onError: @MainActor @escaping (Error) -> Void = { _ in } ) { let accessGranted = RecentsStore.beginAccessing(url) openDocument(withContentsOf: url, display: true) { _, _, error in - if let error { - if accessGranted { - RecentsStore.endAccessing(url) - } - DispatchQueue.main.async { - NSAlert(error: error).runModal() - } - onError(error) - } else { - RecentsStore.documentOpened(at: url) - DispatchQueue.main.async { - NSApp.activate(ignoringOtherApps: true) + DispatchQueue.mainIfNot { + if let error { + if accessGranted { + RecentsStore.endAccessing(url) + } + DispatchQueue.main.async { + NSAlert(error: error).runModal() + } + onError(error) + } else { + RecentsStore.documentOpened(at: url) + DispatchQueue.main.async { + NSApp.activate(ignoringOtherApps: true) + } + onCompletion() } - onCompletion() } } } diff --git a/Sources/WelcomeWindow/Model/RecentsStore.swift b/Sources/WelcomeWindow/Model/RecentsStore.swift index 6aad5f369..aa8b17710 100644 --- a/Sources/WelcomeWindow/Model/RecentsStore.swift +++ b/Sources/WelcomeWindow/Model/RecentsStore.swift @@ -39,7 +39,7 @@ public enum RecentsStore { } } - private static let logger = Logger( + private nonisolated static let logger = Logger( subsystem: Bundle.main.bundleIdentifier ?? "com.example.app", category: "RecentsStore" ) diff --git a/Sources/WelcomeWindow/Utils/DispatchQueue+asyncIfNot.swift b/Sources/WelcomeWindow/Utils/DispatchQueue+asyncIfNot.swift new file mode 100644 index 000000000..a8591c7e3 --- /dev/null +++ b/Sources/WelcomeWindow/Utils/DispatchQueue+asyncIfNot.swift @@ -0,0 +1,22 @@ +// +// DispatchQueue+asyncIfNot.swift +// WelcomeWindow +// +// Created by Khan Winter on 8/28/25. +// + +import Foundation + +extension DispatchQueue { + /// Dispatch an operation to the main queue if it's not already on it. + /// - Parameter operation: The operation to enqueue. + static func mainIfNot(_ operation: @MainActor @escaping () -> Void) { + if Thread.isMainThread { + MainActor.assumeIsolated { + operation() + } + } else { + DispatchQueue.main.async(execute: operation) + } + } +} diff --git a/Sources/WelcomeWindow/Views/WelcomeWindow.swift b/Sources/WelcomeWindow/Views/WelcomeWindow.swift index d167fd1b7..6b6269ad0 100644 --- a/Sources/WelcomeWindow/Views/WelcomeWindow.swift +++ b/Sources/WelcomeWindow/Views/WelcomeWindow.swift @@ -13,7 +13,7 @@ public struct WelcomeWindow: Scene { private let buildActions: (_ dismissWindow: @escaping () -> Void) -> WelcomeActions private let customRecentsList: ((_ dismissWindow: @escaping () -> Void) -> RecentsView)? - private let onDrop: ((_ url: URL, _ dismiss: @escaping () -> Void) -> Void)? + private let onDrop: (@Sendable (_ url: URL, _ dismiss: @escaping () -> Void) -> Void)? private let subtitleView: (() -> SubtitleView)? private let openHandler: WelcomeOpenHandler? @@ -34,7 +34,7 @@ public struct WelcomeWindow: Scene { @ActionsBuilder actions: @escaping (_ dismissWindow: @escaping () -> Void) -> WelcomeActions, customRecentsList: ((_ dismissWindow: @escaping () -> Void) -> RecentsView)? = nil, subtitleView: (() -> SubtitleView)? = nil, - onDrop: ((_ url: URL, _ dismiss: @escaping () -> Void) -> Void)? = nil, + onDrop: (@Sendable (_ url: URL, _ dismiss: @escaping () -> Void) -> Void)? = nil, openHandler: WelcomeOpenHandler? = nil ) { self.iconImage = iconImage @@ -99,7 +99,7 @@ extension WelcomeWindow where RecentsView == EmptyView, SubtitleView == EmptyVie iconImage: Image? = nil, title: String? = nil, @ActionsBuilder actions: @escaping (_ dismissWindow: @escaping () -> Void) -> WelcomeActions, - onDrop: ((_ url: URL, _ dismissWindow: @escaping () -> Void) -> Void)? = nil, + onDrop: (@Sendable (_ url: URL, _ dismissWindow: @escaping () -> Void) -> Void)? = nil, openHandler: WelcomeOpenHandler? = nil ) { self.init( @@ -125,7 +125,7 @@ extension WelcomeWindow where RecentsView == EmptyView { title: String? = nil, subtitleView: @escaping () -> SubtitleView, @ActionsBuilder actions: @escaping (_ dismissWindow: @escaping () -> Void) -> WelcomeActions, - onDrop: ((_ url: URL, _ dismissWindow: @escaping () -> Void) -> Void)? = nil, + onDrop: (@Sendable (_ url: URL, _ dismissWindow: @escaping () -> Void) -> Void)? = nil, openHandler: WelcomeOpenHandler? = nil ) { self.init( @@ -151,7 +151,7 @@ extension WelcomeWindow where SubtitleView == EmptyView { title: String? = nil, @ActionsBuilder actions: @escaping (_ dismissWindow: @escaping () -> Void) -> WelcomeActions, customRecentsList: ((_ dismissWindow: @escaping () -> Void) -> RecentsView)? = nil, - onDrop: ((_ url: URL, _ dismissWindow: @escaping () -> Void) -> Void)? = nil, + onDrop: (@Sendable (_ url: URL, _ dismissWindow: @escaping () -> Void) -> Void)? = nil, openHandler: WelcomeOpenHandler? = nil ) { self.init( diff --git a/Sources/WelcomeWindow/Views/WelcomeWindowView.swift b/Sources/WelcomeWindow/Views/WelcomeWindowView.swift index edbcb2124..60f68c783 100644 --- a/Sources/WelcomeWindow/Views/WelcomeWindowView.swift +++ b/Sources/WelcomeWindow/Views/WelcomeWindowView.swift @@ -8,6 +8,14 @@ import SwiftUI import AppKit +struct CustomContainer: View { + let createChild: (() -> SubView)? + + var body: some View { + createChild?() + } +} + public struct WelcomeWindowView: View { @Environment(\.dismiss) @@ -22,7 +30,7 @@ public struct WelcomeWindowView: View { @State private var selection: Set = [] private let buildActions: (_ dismissWindow: @escaping () -> Void) -> WelcomeActions - private let onDrop: ((_ url: URL, _ dismiss: @escaping () -> Void) -> Void)? + private let onDrop: (@Sendable (_ url: URL, _ dismiss: @escaping () -> Void) -> Void)? private let customRecentsList: ((_ dismissWindow: @escaping () -> Void) -> RecentsView)? private let subtitleView: (() -> SubtitleView)? private let openHandler: WelcomeOpenHandler? @@ -35,7 +43,7 @@ public struct WelcomeWindowView: View { title: String? = nil, subtitleView: (() -> SubtitleView)? = nil, buildActions: @escaping (_ dismissWindow: @escaping () -> Void) -> WelcomeActions, - onDrop: ((_ url: URL, _ dismiss: @escaping () -> Void) -> Void)? = nil, + onDrop: (@Sendable (_ url: URL, _ dismiss: @escaping () -> Void) -> Void)? = nil, customRecentsList: ((_ dismissWindow: @escaping () -> Void) -> RecentsView)? = nil, openHandler: WelcomeOpenHandler? = nil ) { @@ -60,12 +68,20 @@ public struct WelcomeWindowView: View { } } - public var body: some View { - let dismiss = dismissWindow.callAsFunction - let actions = buildActions(dismiss) - let effectiveOpen = openHandler ?? defaultOpenHandler + var dismiss: () -> Void { + dismissWindow.callAsFunction + } - return HStack(spacing: 0) { + var actions: WelcomeActions { + buildActions(dismiss) + } + + var effectiveOpen: (@MainActor ([URL], @escaping () -> Void) -> Void) { + openHandler ?? defaultOpenHandler + } + + public var body: some View { + HStack(spacing: 0) { WelcomeView( iconImage: iconImage, title: title, @@ -114,11 +130,11 @@ public struct WelcomeWindowView: View { } .onDrop(of: [.fileURL], isTargeted: .constant(true)) { providers in NSApp.activate(ignoringOtherApps: true) - providers.forEach { + providers.forEach { [onDrop, dismissWindow] in _ = $0.loadDataRepresentation(for: .fileURL) { data, _ in if let data, let url = URL(dataRepresentation: data, relativeTo: nil) { Task { @MainActor in - onDrop?(url, dismiss) + onDrop?(url, dismissWindow.callAsFunction) } } } From 6bdfe51bc7de2f7c45c36d4938eaa515a72e7602 Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Thu, 28 Aug 2025 13:37:05 -0500 Subject: [PATCH 2/2] Remove Concurrency Test --- Sources/WelcomeWindow/Views/WelcomeWindowView.swift | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Sources/WelcomeWindow/Views/WelcomeWindowView.swift b/Sources/WelcomeWindow/Views/WelcomeWindowView.swift index 60f68c783..6a484c68f 100644 --- a/Sources/WelcomeWindow/Views/WelcomeWindowView.swift +++ b/Sources/WelcomeWindow/Views/WelcomeWindowView.swift @@ -8,14 +8,6 @@ import SwiftUI import AppKit -struct CustomContainer: View { - let createChild: (() -> SubView)? - - var body: some View { - createChild?() - } -} - public struct WelcomeWindowView: View { @Environment(\.dismiss)