Handling Non-Sendable Types in Swift Concurrency
Problem Statement
When working with NSExtensionContext in Swift concurrency contexts, you may encounter warnings about non-sendable types crossing actor boundaries. These warnings indicate compatibility violations with Swift 6's strict concurrency checks. The core issue arises because:
NSExtensionContextand related classes likeNSItemProviderare not marked asSendable- Accessing
MainActor-isolated properties from a non-main thread triggers isolation violations - Swift's compiler prevents potential data races when crossing actor boundaries with non-sendable types
Your original code triggers warnings because it modifies a main-actor-isolated url property from a non-isolated closure:
attachment.loadItem(forTypeIdentifier: "public.url") { url, error in
guard let url else { return }
self.url = url as? URL // Warning: Main actor isolation violation
}This occurs because Foundation's NSItemProvider.loadItem completion handler executes on a background thread, making it unsafe to update main-actor properties.
Recommended Solution
The optimal approach combines using Swift's async API variants with proper actor isolation management:
Step-by-Step Implementation
- Use
asyncversion ofloadItem - Annotate UI updates with
@MainActor - Suppress legacy warnings with
@preconcurrencyimports
// Import with concurrency workaround
@preconcurrency import UIKit
class ShareViewController: UIViewController {
var url: URL? // MainActor-isolated property
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 attachment.hasItemConformingToTypeIdentifier("public.url") {
do {
let item = try await attachment.loadItem(forTypeIdentifier: "public.url")
await updateURL(with: item) // Correct actor crossing
} catch {
print("Error loading item: \(error)")
}
}
}
}
@MainActor
private func updateURL(with item: NSSecureCoding?) {
self.url = item as? URL
}
}Key Improvements
- Uses the modern async
loadItemAPI instead of completion handlers - Isolates property updates to
@MainActorwith a dedicated method - Avoids unsafe cross-actor property mutations
- Uses
@preconcurrencyattribute to suppress unannotated Foundation warnings
Why This Works
Actor Isolation
By marking updateURL with @MainActor, we explicitly declare our intent to modify UI-related state on the main thread. This aligns with Swift's actor isolation model and prevents concurrent mutations.
TIP
The temporary @preconcurrency import is safe because:
- It allows importing unannotated modules without warnings
- Maintains full runtime safety guarantees
- Can be removed once Apple fully annotates UIKit/Foundation for Sendable
Avoiding Unsafe Workarounds
While @unchecked Sendable might appear as a quick fix:
// Not recommended
extension NSExtensionContext: @unchecked Sendable {}This approach bypasses compiler safety checks and risks introducing subtle concurrency bugs in Swift 6.
Alternative Closure-Based Solution
If you must use the legacy closure API:
attachment.loadItem(forTypeIdentifier: "public.url") { [weak self] url, error in
guard let url else { return }
Task { @MainActor in
self?.url = url as? URL // Safely isolated to MainActor
}
}Important
This closure-based approach:
- Requires manual dispatch back to main actor
- Is less efficient than native async/await
- Still triggers non-Sendable warnings for types like
NSSecureCoding
Migration Recommendations
- Prefer
async/awaitAPIs when available - Wrap main thread updates using
@MainActororMainActor.run - Use
@preconcurrencyimports as a bridge solution - Audit all closure-based handlers for cross-actor mutations
- Gradually replace closures with async methods as Apple updates frameworks
As Swift 6 matures, Apple will add proper Sendable annotations to Foundation and UIKit types. Until then, these patterns ensure safe concurrency practices while maintaining compatibility with future Swift versions.