Skip to content

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:

  1. NSExtensionContext and related classes like NSItemProvider are not marked as Sendable
  2. Accessing MainActor-isolated properties from a non-main thread triggers isolation violations
  3. 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:

swift
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.

The optimal approach combines using Swift's async API variants with proper actor isolation management:

Step-by-Step Implementation

  1. Use async version of loadItem
  2. Annotate UI updates with @MainActor
  3. Suppress legacy warnings with @preconcurrency imports
swift
// 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

  1. Uses the modern async loadItem API instead of completion handlers
  2. Isolates property updates to @MainActor with a dedicated method
  3. Avoids unsafe cross-actor property mutations
  4. 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:

swift
// 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:

swift
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

  1. Prefer async/await APIs when available
  2. Wrap main thread updates using @MainActor or MainActor.run
  3. Use @preconcurrency imports as a bridge solution
  4. Audit all closure-based handlers for cross-actor mutations
  5. 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.