Reputation: 32093
I am doing things, I think, by the book. However, I keep running into compiler warnings/errors.
Here's the affected code, which is in a View
's .onChange(of:_:)
closure, where currentMediaTitle
is just a String
, and player
is just a AVPlayer
:
currentMediaTitle = ""
Task {
guard let asset = player.currentItem?.asset else {
currentMediaTitle = nil
return
}
let allMetadata = try await asset.load(.metadata) // WARNING: Non-sendable type '[AVMetadataItem]' returned by implicitly asynchronous call to nonisolated function cannot cross actor boundary
guard let titleMetadata = allMetadata.first(where: { item in
item.identifier == AVMetadataIdentifier.commonIdentifierTitle
})
else {
currentMediaTitle = nil
return
}
let titleValue = try await titleMetadata.load(.value) // WARNING: Passing argument of non-sendable type 'AVMetadataItem' outside of main actor-isolated context may introduce data races
let title = (titleValue as? NSString) as String?
self.currentMediaTitle = title
}
here's a screenshot version if that's better for you:
So there's a few things that... feel paradoxical to me:
.metadata
are deprecated in favor of this new structured concurrency .load(.metadata)
method, but at the same time the value returned from this new method then also breaks the contracts of structured concurrencyAVAsset
andor AVMetadataItem
as actor
s for some reason, when they're both class
esAVMetadataItem
, but I'm calling a method on it. Maybe I can understand how it thinks this because Swift seems to treat methods as static functions which take their self
as their first argument, but even then I don't see how this could possibly break structured concurrency since it's self-scopedI even tried a few things to get this all to happen on the MainActor
but it still gave me the exact same warnings so like.. I'm kinda stuck here.
What do I do to resolve these warnings?
Upvotes: 2
Views: 586
Reputation: 271410
The code in the Task { ... }
is main-actor isolated, but load
is not, so non-sendable types like AVMetadataItem
cannot be passed between them, crossing actor boundaries.
You can use Task.detached
to run the code off of the main actor, but this will still capture self
(assuming currentMediaTitle
is a member of self
), which is probably non-sendable. You still get a warning if you turn on strict concurrency checking.
It seems like you are fetching the title of some AVAsset
, so I would write this as an extension on AVAsset
:
extension AVAsset {
func mediaTitle() async throws -> String? {
let allMetadata = try await load(.metadata)
guard let titleMetadata = allMetadata.first(where: { item in
item.identifier == AVMetadataIdentifier.commonIdentifierTitle
})
else {
return nil
}
let titleValue = try await titleMetadata.load(.value)
let title = (titleValue as? NSString) as String?
return title
}
}
and now you can do:
Task {
currentMediaTitle = try await player.currentItem?.asset.mediaTitle()
}
This again sends an AVAsset
across actor boundaries, and you will get a warning if you turn on strict concurrency checking. You can silence this warning using @preconcurrency import AVFoundation
, and this will probably not be an issue after region-based actor isolation is implemented.
Upvotes: 3