iOS Share Extension Warning: Passing argument of non-sendable type outside of main actor-isolated context may introduce data races

Consider this simple miniature of my iOS Share Extension:

import SwiftUI
import Photos

class ShareViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        if let itemProviders = (extensionContext?.inputItems.first as? NSExtensionItem)?.attachments {
            let hostingView = UIHostingController(rootView: ShareView(extensionContext: extensionContext, itemProviders: itemProviders))
            hostingView.view.frame = view.frame
            view.addSubview(hostingView.view)
        }
        
    }
}

struct ShareView: View {
    var extensionContext: NSExtensionContext?
    var itemProviders: [NSItemProvider]
    
    var body: some View {
        VStack{}
        .task{
            await extractItems()
        }
    }
    
    func extractItems() async {
        guard let itemProvider = itemProviders.first else { return }
        guard itemProvider.hasItemConformingToTypeIdentifier(UTType.url.identifier) else { return }

        do {
            guard let url = try await itemProvider.loadItem(forTypeIdentifier: UTType.url.identifier) as? URL else { return }
            try await downloadAndSaveMedia(reelURL: url.absoluteString)
            extensionContext?.completeRequest(returningItems: [])
            
        }
        catch {}
    }
}

On the line 34

guard let url = try await itemProvider.loadItem

... I get these warnings:

  1. Passing argument of non-sendable type '[AnyHashable : Any]?' outside of main actor-isolated context may introduce data races; this is an error in the Swift 6 language mode

1.1. Generic enum 'Optional' does not conform to the 'Sendable' protocol (Swift.Optional)

  1. Passing argument of non-sendable type 'NSItemProvider' outside of main actor-isolated context may introduce data races; this is an error in the Swift 6 language mode

2.2. Class 'NSItemProvider' does not conform to the 'Sendable' protocol (Foundation.NSItemProvider)

How to fix them in Xcode 16?

Please provide a solution which works, and not the one which might (meaning you run the same code in Xcode, add your solution and see no warnings).

I tried

  1. Decorating everything with @MainActors
  2. Using @MainActor in the .task
  3. @preconcurrency import
  4. Decorating everything with @preconcurrency
  5. Playing around with nonisolated
Answered by darkpaw in 827562022

I can't provide you with a full, completely working solution, but this doesn't cause any errors:

class ShareViewController: UIViewController {
	override func viewDidLoad() {
		super.viewDidLoad()
	}

	override func loadView() {
		super.loadView()

		if let inputItem = extensionContext!.inputItems.first as? NSExtensionItem {
			if let itemProvider = inputItem.attachments?.first {
				itemProvider.loadItem(forTypeIdentifier: UTType.url.identifier as String) { [unowned self] (item, error) in
					let contentView = ShareView(extensionContext: extensionContext, url: item as! URL)

					DispatchQueue.main.async {
						let hostingView = UIHostingController(rootView: contentView)
						hostingView.view.frame = self.view.frame
						self.view.addSubview(hostingView.view)
					}
				}
			}
		}
	}
}

struct ShareView: View {
	var extensionContext: NSExtensionContext?
	var url: URL

	var body: some View {
		VStack{}
			.task{
				await extractItems()
			}
	}

	func extractItems() async {
		try await downloadAndSaveMedia(reelURL: url.absoluteString)
		extensionContext?.completeRequest(returningItems: [])
	}
}

In your code you get all the attachments here: if let itemProviders = (extensionContext?.inputItems.first as? NSExtensionItem)?.attachments { and you send that array of NSItemProvider to ShareView(), but you then get just the first attachment here (first line of extractItems()): guard let itemProvider = itemProviders.first else { return }, so in my code I'm just using the first attachment.

The distinction is that I do all that stuff in the loadView() method rather than in ShareView().

Might work for you, might not, but I don't think you've tried this. You can probably tidy it up a little.

Accepted Answer

I can't provide you with a full, completely working solution, but this doesn't cause any errors:

class ShareViewController: UIViewController {
	override func viewDidLoad() {
		super.viewDidLoad()
	}

	override func loadView() {
		super.loadView()

		if let inputItem = extensionContext!.inputItems.first as? NSExtensionItem {
			if let itemProvider = inputItem.attachments?.first {
				itemProvider.loadItem(forTypeIdentifier: UTType.url.identifier as String) { [unowned self] (item, error) in
					let contentView = ShareView(extensionContext: extensionContext, url: item as! URL)

					DispatchQueue.main.async {
						let hostingView = UIHostingController(rootView: contentView)
						hostingView.view.frame = self.view.frame
						self.view.addSubview(hostingView.view)
					}
				}
			}
		}
	}
}

struct ShareView: View {
	var extensionContext: NSExtensionContext?
	var url: URL

	var body: some View {
		VStack{}
			.task{
				await extractItems()
			}
	}

	func extractItems() async {
		try await downloadAndSaveMedia(reelURL: url.absoluteString)
		extensionContext?.completeRequest(returningItems: [])
	}
}

In your code you get all the attachments here: if let itemProviders = (extensionContext?.inputItems.first as? NSExtensionItem)?.attachments { and you send that array of NSItemProvider to ShareView(), but you then get just the first attachment here (first line of extractItems()): guard let itemProvider = itemProviders.first else { return }, so in my code I'm just using the first attachment.

The distinction is that I do all that stuff in the loadView() method rather than in ShareView().

Might work for you, might not, but I don't think you've tried this. You can probably tidy it up a little.

@darkpaw thank you very much, it helped :)

iOS Share Extension Warning: Passing argument of non-sendable type outside of main actor-isolated context may introduce data races
 
 
Q