I'm trying to understand how to use an Apple class, without Sendable, in an async context without getting warnings that this won't work in Swift 6.
Weapon of choice is NSExtensionContext
which I need to fetch a URL that's been passed into a share extension.
This is my original code that simply fetches a URL from the extension context. With the new concurrency checking enabled it gives the warning:
Main actor-isolated property 'url' can not be mutated from a Sendable closure; this is an error in Swift 6
class ShareViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
fetchURL()
}
private func fetchURL() {
guard let extensionContext = self.extensionContext,
let item = extensionContext.inputItems.first as? NSExtensionItem,
let attachments = item.attachments else { return }
for attachment in attachments {
if attachment.hasItemConformingToTypeIdentifier("public.url") {
attachment.loadItem(forTypeIdentifier: "public.url") { url, error in
guard let url else { return }
self.url = url as? URL <-- WARNING! Main actor-isolated property 'url' can not be mutated from a Sendable closure; this is an error in Swift 6
}
}
}
}
}
I understand the function is called on the MainActor
but the extensionContext
can be used on any actor which is the reason for the complaint.
Firstly can I perhaps mark the url property as Sendable
so it can be modified from any actor?
Trying something different, I modified it to use the latest async/await
versions of extensionContect
.
override func viewDidLoad() {
super.viewDidLoad()
Task {
await fetchURL()
}
}
private func fetchURL() async {
guard let extensionContext = self.extensionContext,
let item = extensionContext.inputItems.first as? NSExtensionItem,
let attachments = item.attachments else { return }
for attachment in attachments {
if let url = try? await attachment.loadItem(forTypeIdentifier: "public.url") as? URL { <-- WARNINGS!
self.url = url
}
}
}
This actually gives me 4 warnings on the same line!
Non-sendable type 'any NSSecureCoding' returned by implicitly asynchronous call to nonisolated function cannot cross actor boundary
Passing argument of non-sendable type '[AnyHashable : Any]?' outside of main actor-isolated context may introduce data races
Passing argument of non-sendable type '[AnyHashable : Any]?' outside of main actor-isolated context may introduce data races
Passing argument of non-sendable type 'NSItemProvider' outside of main actor-isolated context may introduce data races
Let's try detaching the Task so it runs on a new actor:
private func fetchURL() async {
guard let extensionContext = self.extensionContext,
let item = extensionContext.inputItems.first as? NSExtensionItem,
let attachments = item.attachments else { return }
Task.detached {
for attachment in attachments { <-- WARNING! Capture of 'attachments' with non-sendable type '[NSItemProvider]' in a `@Sendable` closure
let attachment = attachment
if let url = try? await attachment.loadItem(forTypeIdentifier: "public.url") as? URL {
await MainActor.run { [weak self] in
self?.url = url
}
}
}
}
}
Just the 1 warning with this:
Capture of 'attachments' with non-sendable type '[NSItemProvider]' in a
@Sendable
closure
Final try, let's put everything in the detached actor. This requires accessing the extensionContext
asynchronously using await
:
private func fetchURL() async {
Task.detached { [weak self] in
guard let extensionContext = await self?.extensionContext, <-- WARNING! Non-sendable type 'NSExtensionContext?' in implicitly asynchronous access to main actor-isolated property 'extensionContext' cannot cross actor boundary
let item = extensionContext.inputItems.first as? NSExtensionItem,
let attachments = item.attachments else { return }
for attachment in attachments {
let attachment = attachment
if let url = try? await attachment.loadItem(forTypeIdentifier: "public.url") as? URL {
await MainActor.run { [weak self] in
self?.url = url
}
}
}
}
}
We get the error:
Non-sendable type 'NSExtensionContext?' in implicitly asynchronous access to main actor-isolated property 'extensionContext' cannot cross actor boundary
I know 1 way to clear all the warnings:
extension NSExtensionContext: @unchecked Sendable {}
The problem I have with this, is using @unchecked
seems to be like telling the compiler to just ignore the consequences.
What would be the correct way to use this extensionContext
in a UIViewController
that runs on @MainActor
?
NSExtensionContext
areget
only theextension
should be ok because there isn't any mutating. – Trigonous