Reputation: 8376
I have a function that looks like this:
public final class MyClass {
static let shared = MyClass()
public func doSomething(dictionary: [String: Any]? = nil) async -> UIViewController {
return await UIViewController()
}
}
I call it like this inside a UIViewController
:
final class MyViewController: UIViewController {
@IBAction private func tapButton() {
Task {
let vc = await MyClass.shared.doSomething()
}
}
}
But it gives me a warning:
Non-sendable type '[String : Any]?' exiting main actor-isolated context in call to non-isolated instance method 'doSomething(dictionary:)' cannot cross actor boundary
Note that you must have Strict Concurrency Checking
in your Build Settings set to Complete
or Targeted
for this to show up in Xcode.
I know that functions inside a UIViewController
are isolated to the MainActor and I want tapButton()
to be. But what can I do about this to avoid this warning?
Upvotes: 10
Views: 5026
Reputation: 271420
The dictionary is said to "exit main actor-isolated context" and "cross actor boundary" because MyViewController
is a @MainActor
class, but MyClass
isn't. Whatever will be running doSomething
is not the main actor, but the cooperative thread pool.
It is not safe for a non-Sendable
type like [String: Any]?
to cross actor boundaries. [String: Any]?
is not Sendable
because Any
is not Sendable
. Yes, you are only passing nil
here, which should be safe, but Swift looks at the types only.
There are a few ways to solve this. Here are some that I thought of:
[String: any Sendable]?
insteadMyClass
or doSomething
with @MainActor
Task.detached
instead of Task.init
, so that the cooperative thread pool runs the task too. Task.init
will inherit the current actor context, and at tapButton
, it is the main actor.Upvotes: 11
Reputation: 299345
You can't pass [String: Any]
across concurrency boundaries under "targeted" concurrency warnings, since it's not (and can't be) Sendable. Even though it's a default parameter, it's still being created in the calling context and then passed to the receiving context. To avoid that, you need to create the default parameter in the receiving context. Default parameters are just shortcuts for explicit overloads, so you can write the overload explicitly instead:
// Remove the default parameter here
public func doSomething(dictionary: [String: Any]?) async -> UIViewController {
return await UIViewController()
}
// And define it explicitly as an overload
public func doSomething() async -> UIViewController {
return await doSomething(dictionary: nil)
}
With this, the call to doSomething()
doesn't create a Sendable problem.
That said, I expect that MyClass should actually be @MainActor
(or at least doSomething
). Creating a UIViewController on a non-main queue is legal if I remember correctly (though it might not be), but no method is legal to call on it outside the main queue.
Upvotes: -1