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:
NSExtensionContext
and related classes likeNSItemProvider
are 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
async
version ofloadItem
- Annotate UI updates with
@MainActor
- Suppress legacy warnings with
@preconcurrency
imports
// 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
loadItem
API instead of completion handlers - Isolates property updates to
@MainActor
with a dedicated method - Avoids unsafe cross-actor property mutations
- Uses
@preconcurrency
attribute 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/await
APIs when available - Wrap main thread updates using
@MainActor
orMainActor.run
- Use
@preconcurrency
imports 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.