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?
2
Answers
The dictionary is said to "exit main actor-isolated context" and "cross actor boundary" because
MyViewController
is a@MainActor
class, butMyClass
isn’t. Whatever will be runningdoSomething
is not the main actor, but the cooperative thread pool.It is not safe for a non-
Sendable
type like[String: Any]?
to cross cross actor boundaries.[String: Any]?
is notSendable
becauseAny
is notSendable
. Yes, you are only passingnil
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
ordoSomething
with@MainActor
Task.detached
instead ofTask.init
, so that the cooperative thread pool runs the task too.Task.init
will inherit the current actor context, and attapButton
, it is the main actor.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:With this, the call to
doSomething()
doesn’t create a Sendable problem.That said, I expect that MyClass should actually be
@MainActor
(or at leastdoSomething
). 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.