Skip to content

Commit a1f22fc

Browse files
authored
fix: swift 6 concurrency (#63)
1 parent 012bda2 commit a1f22fc

16 files changed

+119
-167
lines changed

Front Row.xcodeproj/project.pbxproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@
375375
STRING_CATALOG_GENERATE_SYMBOLS = YES;
376376
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
377377
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
378-
SWIFT_VERSION = 5.0;
378+
SWIFT_VERSION = 6.0;
379379
};
380380
name = Debug;
381381
};
@@ -434,7 +434,7 @@
434434
SDKROOT = macosx;
435435
STRING_CATALOG_GENERATE_SYMBOLS = YES;
436436
SWIFT_COMPILATION_MODE = wholemodule;
437-
SWIFT_VERSION = 5.0;
437+
SWIFT_VERSION = 6.0;
438438
};
439439
name = Release;
440440
};

Front Row/FrontRowApp.swift

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,13 @@ import SwiftUI
1212
@main
1313
struct FrontRowApp: App {
1414
@NSApplicationDelegateAdaptor private var appDelegate: AppDelegate
15-
@State private var playEngine: PlayEngine
16-
@State private var presentedViewManager: PresentedViewManager
17-
@State private var windowController: WindowController
15+
@State private var playEngine = PlayEngine.shared
16+
@State private var presentedViewManager = PresentedViewManager.shared
17+
@State private var windowController = WindowController.shared
1818
private let updaterController: SPUStandardUpdaterController
1919
private let keyDownListener = KeyDownListener()
2020

2121
init() {
22-
self._playEngine = .init(wrappedValue: .shared)
23-
self._presentedViewManager = .init(wrappedValue: .shared)
24-
self._windowController = .init(wrappedValue: .shared)
25-
2622
updaterController = SPUStandardUpdaterController(
2723
startingUpdater: true,
2824
updaterDelegate: nil,
@@ -39,7 +35,6 @@ struct FrontRowApp: App {
3935
ContentView()
4036
.preferredColorScheme(.dark)
4137
.ignoresSafeArea()
42-
.environment(playEngine)
4338
.navigationTitle(playEngine.fileURL?.lastPathComponent ?? "Front Row")
4439
.if(playEngine.isLocalFile) { view in
4540
view.navigationDocument(playEngine.fileURL!)
@@ -75,18 +70,15 @@ struct FrontRowApp: App {
7570
}
7671
}
7772
.restorationBehavior(.disabled)
73+
.environment(playEngine)
74+
.environment(presentedViewManager)
75+
.environment(windowController)
7876
.commands {
7977
AppCommands(updater: updaterController.updater)
80-
FileCommands(playEngine: $playEngine)
81-
ViewCommands(
82-
playEngine: $playEngine,
83-
windowController: $windowController)
84-
PlaybackCommands(
85-
playEngine: $playEngine,
86-
presentedViewManager: $presentedViewManager)
87-
WindowCommands(
88-
playEngine: $playEngine,
89-
windowController: $windowController)
78+
FileCommands()
79+
ViewCommands()
80+
PlaybackCommands()
81+
WindowCommands()
9082
HelpCommands()
9183
}
9284
}

Front Row/Main Menu/FileCommands.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ import AVKit
99
import SwiftUI
1010

1111
struct FileCommands: Commands {
12-
@Binding var playEngine: PlayEngine
13-
1412
var body: some Commands {
1513
CommandGroup(replacing: .newItem) {
1614
Button {
@@ -38,19 +36,21 @@ struct FileCommands: Commands {
3836
Divider()
3937

4038
Button {
41-
guard let item = PlayEngine.shared.player.currentItem else { return }
42-
guard let asset = item.asset as? AVURLAsset else { return }
39+
guard let item = PlayEngine.shared.player.currentItem,
40+
let asset = item.asset as? AVURLAsset
41+
else { return }
4342
NSWorkspace.shared.activateFileViewerSelecting([asset.url])
4443
} label: {
4544
Text(
4645
"Show in Finder",
4746
comment: "Show the currently playing file in Finder"
4847
)
4948
}
50-
.disabled(!playEngine.isLocalFile)
49+
.disabled(!PlayEngine.shared.isLocalFile)
5150
}
5251
}
5352

53+
@MainActor
5454
private func showOpenFileDialog() async {
5555
let panel = NSOpenPanel()
5656
panel.allowedContentTypes = PlayEngine.supportedFileTypes

Front Row/Main Menu/PlaybackCommands.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import AVKit
99
import SwiftUI
1010

1111
struct PlaybackCommands: Commands {
12-
@Binding var playEngine: PlayEngine
13-
@Binding var presentedViewManager: PresentedViewManager
12+
@Bindable private var playEngine = PlayEngine.shared
13+
@Bindable private var presentedViewManager = PresentedViewManager.shared
1414

1515
var body: some Commands {
1616
CommandMenu("Playback") {
@@ -151,7 +151,7 @@ struct PlaybackCommands: Commands {
151151
if let group = playEngine.audioGroup {
152152
Picker("Audio Track", selection: $playEngine.audioTrack) {
153153
Text("Off").tag(nil as AVMediaSelectionOption?)
154-
ForEach(group.options) { option in
154+
ForEach(group.options, id: \.stableID) { option in
155155
Text(verbatim: option.displayName).tag(Optional(option))
156156
}
157157
}

Front Row/Main Menu/ViewCommands.swift

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,18 @@ import AVKit
99
import SwiftUI
1010

1111
struct ViewCommands: Commands {
12-
@Binding var playEngine: PlayEngine
13-
@Binding var windowController: WindowController
14-
1512
var body: some Commands {
1613
CommandGroup(replacing: .toolbar) {
1714
Button {
1815
NSApplication.shared.mainWindow?.toggleFullScreen(nil)
1916
} label: {
20-
Text(windowController.isFullscreen ? "Exit Full Screen" : "Enter Full Screen")
17+
Text(
18+
WindowController.shared.isFullscreen
19+
? "Exit Full Screen" : "Enter Full Screen")
2120
}
2221
.keyboardShortcut(.return, modifiers: [])
2322

24-
Toggle(isOn: $windowController.isOnTop) {
23+
Toggle(isOn: Bindable(WindowController.shared).isOnTop) {
2524
Text("Float on Top")
2625
}
2726

@@ -32,14 +31,14 @@ struct ViewCommands: Commands {
3231
}
3332

3433
@ViewBuilder private var subtitlePicker: some View {
35-
if let group = playEngine.subtitleGroup {
36-
Picker("Subtitle", selection: $playEngine.subtitle) {
34+
if let group = PlayEngine.shared.subtitleGroup {
35+
Picker("Subtitle", selection: Bindable(PlayEngine.shared).subtitle) {
3736
Text("Off").tag(nil as AVMediaSelectionOption?)
3837

3938
let optionsWithoutForcedSubs = group.options.filter {
4039
!$0.displayName.contains("Forced")
4140
}
42-
ForEach(optionsWithoutForcedSubs) {
41+
ForEach(optionsWithoutForcedSubs, id: \.stableID) {
4342
option in
4443
Text(verbatim: option.displayName).tag(Optional(option))
4544
}

Front Row/Main Menu/WindowCommands.swift

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@
88
import SwiftUI
99

1010
struct WindowCommands: Commands {
11-
@Binding var playEngine: PlayEngine
12-
@Binding var windowController: WindowController
13-
1411
var body: some Commands {
1512
CommandGroup(after: .windowSize) {
1613
Section {
@@ -23,7 +20,7 @@ struct WindowCommands: Commands {
2320
)
2421
}
2522
.keyboardShortcut("0", modifiers: [.command])
26-
.disabled(!playEngine.isLoaded || windowController.isFullscreen)
23+
.disabled(!PlayEngine.shared.isLoaded || WindowController.shared.isFullscreen)
2724
}
2825
}
2926
}

Front Row/Support/Extensions.swift

Lines changed: 12 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -51,50 +51,19 @@ struct AnyDropDelegate: DropDelegate {
5151
}
5252
}
5353

54-
extension NSItemProvider: @unchecked Sendable {}
55-
5654
extension NSItemProvider {
57-
func loadObject<T>(ofClass: T.Type) async throws -> T? where T: NSItemProviderReading {
58-
try await withCheckedThrowingContinuation { continuation in
59-
_ = loadObject(ofClass: ofClass) { data, error in
60-
if let error {
61-
continuation.resume(throwing: error)
62-
return
63-
}
64-
65-
guard let object = data as? T else {
66-
continuation.resume(returning: nil)
67-
return
68-
}
69-
70-
continuation.resume(returning: object)
55+
/// Load a file URL from the item provider.
56+
func loadFileURL(completion: @escaping @Sendable (URL?) -> Void) {
57+
loadItem(forTypeIdentifier: "public.file-url", options: nil) { data, _ in
58+
guard let data = data as? Data,
59+
let url = URL(dataRepresentation: data, relativeTo: nil)
60+
else {
61+
completion(nil)
62+
return
7163
}
64+
completion(url)
7265
}
7366
}
74-
75-
func loadObject<T>(ofClass: T.Type) async throws -> T?
76-
where T: _ObjectiveCBridgeable, T._ObjectiveCType: NSItemProviderReading {
77-
try await withCheckedThrowingContinuation { continuation in
78-
_ = loadObject(ofClass: ofClass) { data, error in
79-
if let error {
80-
continuation.resume(throwing: error)
81-
return
82-
}
83-
84-
guard let data else {
85-
continuation.resume(returning: nil)
86-
return
87-
}
88-
89-
continuation.resume(returning: data)
90-
}
91-
}
92-
}
93-
94-
/// Get a URL from the item provider, if any.
95-
func getURL() async -> URL? {
96-
try? await loadObject(ofClass: URL.self)
97-
}
9867
}
9968

10069
extension NSSize {
@@ -131,8 +100,9 @@ extension NSSize {
131100
}
132101
}
133102

134-
extension AVMediaSelectionOption: Identifiable {
135-
public var id: String {
103+
extension AVMediaSelectionOption {
104+
/// Provides a stable identifier for the option.
105+
var stableID: String {
136106
let dict = propertyList() as? NSDictionary
137107
guard let dict, let id = dict.value(forKey: "MediaSelectionOptionsPersistentID") as? Int
138108
else {

Front Row/Support/KeyDownListener.swift

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,20 +37,25 @@ final class KeyDownListener {
3737
return event
3838
}
3939

40-
let allWindows = NSApp.windows
41-
let firstResponders = allWindows.compactMap { $0.firstResponder }
42-
let fieldEditors = firstResponders.filter { ($0 as? NSText)?.isEditable == true }
43-
guard fieldEditors.isEmpty else { return event }
44-
4540
switch command {
4641
case .escape:
47-
if !WindowController.shared.isFullscreen {
48-
NSApp.hide(nil)
49-
PlayEngine.shared.pause()
50-
return nil
42+
let shouldHandle = MainActor.assumeIsolated {
43+
let allWindows = NSApp.windows
44+
let firstResponders = allWindows.compactMap { $0.firstResponder }
45+
let fieldEditors = firstResponders.filter {
46+
($0 as? NSText)?.isEditable == true
47+
}
48+
guard fieldEditors.isEmpty else { return false }
49+
50+
if !WindowController.shared.isFullscreen {
51+
NSApp.hide(nil)
52+
PlayEngine.shared.pause()
53+
return true
54+
}
55+
return false
5156
}
57+
return shouldHandle ? nil : event
5258
}
53-
return event
5459
}
5560
}
5661

Front Row/Support/NowPlayable+RemoteCommands.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import MediaPlayer
99

1010
extension NowPlayable {
11-
func setupRemoteCommandHandlers(playEngine: PlayEngine) {
11+
@MainActor func setupRemoteCommandHandlers(playEngine: PlayEngine) {
1212
let commandCenter = MPRemoteCommandCenter.shared()
1313

1414
commandCenter.playCommand.isEnabled = true

Front Row/Support/NowPlayable.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ struct NowPlayableDynamicMetadata {
2929
let duration: Float
3030
}
3131

32+
@MainActor
3233
final class NowPlayable {
3334

3435
static let shared = NowPlayable()

0 commit comments

Comments
 (0)