How to use non-Sendable type in async reducer code?

I am working on an app that uses modern TCA (ReducerProtocol and async code). In one of my action handlers in reducer, I have this:

return .run { send in
  let result = await environment.mediaManager.storePhotoOrVideo(
    fromItemProvider: itemProvider,
    mediaSessionId: mediaSessionId,
    configuration: .default
  await send(.mediaManagerStoredPhoto(result))

This is nice and expressive and shows exactly what I want to do. Return type of the environment object function call is Result<URL, MediaManagerError>. I receive itemProvider and process it, receiving the result, and pass it on to another action.

Here is the problem. NSItemProvider is an old type that is not Sendable, and most likely will never be. So I get this warning. Which will be an error further down the road, as Swift keeps strengthening Sendable conformance checking.

Is there a way to use some other API or approach, so that I could keep using non-sendable NSItemProvider here, without any warnings (future errors)?

1 Like

Since NSItemProvider is part of foundation, does adding @preconcurrency to the Foundation import fix the issue?

@preconcurrency import Foundation

Using @preconcurrency import Foundation doesn’t change anything. The warning remains there.

Just in case, mentioning the versions – I’m using Xcode 14.2 (14C18) which has Swift 5.7.2.

Can you wrap the item provider in an ActorIsolated?

1 Like

Indeed! Thank you :heart_eyes_cat: ActorIsolated is the answer.

Is it the answer though? NSItemProvider is a class, so all ActorIsolated does is just protects access to the NSItemProvider instance itself. It doesn't protect from any potential asynchronous internal changes inside of NSItemProvider. In other words, it doesn't actually make it Sendable.
It silences the warning, but is it not the same as

extension NSItemProvider: @unchecked Sendable {}

Or am I wrong?

I think you're correct, but in some cases there may not be much of an alternative, as the internals of NSItemProvider may not be able to be affected by the end user, and some APIs require its usage. That said, if there's a way to not use NSItemProvider and instead use simpler, natively-Sendable data types, that would probably be safer.

Now that I look at ActorIsolated I'm realizing that value should not be public. You should be forced to go through withValue to access the underlying value. That's how LockIsolated was designed, so I think it was just an oversight for ActorIsolated.

1 Like

A few Xcode versions forward, and indeed, the warnings are back.

Xcode 14.2 says:

Xcode 14.3 beta 3 says:

It really does look like there is no good solution to use non-Sendable types in TCA reducers, and what I should do is isolate NSItemProvider handling to an outside service, and interact with it through dependencies (formely known as environment). I can still manage the control flow with the reducer, but I cannot cleanly move the actual NSItemProvider objects (or any other non-Sendable types) through it.