swiftmacosavplayerappkitmpmediaitem

Swift data race with AppKit MPMediaItemArtwork function


Consider the code below. It uses the AppKit (macOS) MPMediaItemArtwork function to set the artwork in MPNowPlayingInfoCenter.

During runtime, when compiled with Swift 6, this gives the warning:

warning: data race detected: @MainActor function at MPMediaItemArtwork/ContentView.swift:22 was not called on the main thread

It seems that the requestHandler is not called on the main thread. Adding @MainActor in doesn't seem to make any difference. I may be a little rusty on the Objective-C side, but I can't figure out how to return the image on the main thread as it won't let me use a Task with a return value, as it doesn't support concurrency.

import SwiftUI
import MediaPlayer

struct ContentView: View {
    var body: some View {
        Text("Test")
            .padding()
            .task {
                await MainActor.run {
                    let nowPlayingInfoCenter = MPNowPlayingInfoCenter.default()
                    var nowPlayingInfo = [String: Any]()
                    let image = NSImage(named: "image")!
                    // warning: data race detected: @MainActor function at MPMediaItemArtwork/ContentView.swift:22 was not called on the main thread
                    nowPlayingInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: image.size, requestHandler: { _ in
                        // Not on main thread here!
                        return image
                    })
                    
                    nowPlayingInfoCenter.nowPlayingInfo = nowPlayingInfo
                }
            }
    }
}

Here is a version I have updated to use async/await, however it still gives the same error on the same line:

extension MPMediaItemArtwork: @unchecked Swift.Sendable {
    
    @MainActor
    static func with(image: NSImage) async -> MPMediaItemArtwork {
        await withCheckedContinuation { [image] continuation in
            continuation.resume(returning: MPMediaItemArtwork(boundsSize: image.size) { [image] _ in
                return image
            })
        }
    }
}

.task {
    nowPlayingInfo[MPMediaItemPropertyArtwork] = await MPMediaItemArtwork.with(image: image)
}

Solution

  • It turns out there is an easy fix here. Simply marking the completion handler as @Sendable.

    let artwork = MPMediaItemArtwork(boundsSize: image.size, requestHandler: { @Sendable _ in
        return image
    })
    

    I'm still confused as to why there was no compiler warning or error here, but hopefully that's something Apple can/will fix.