I have the following operator in a Combine pipeline, which must be run on the main thread, before returning to the global dispatch queue. That's because FooManager.shared.foo
is on the MainActor
:
extension AnyPublisher where Output == (Foo, Bar), Failure == Error {
func checkForFoo() -> AnyPublisher<Output, Failure> {
self
.receive(on: RunLoop.main)
.tryMap { x, y in
let isFoo = FooManager.shared.foo // Error here
if isFoo {
guard FooManager.shared.bar == true else {
throw MyError.cancelled
}
}
return (x, y)
}
.receive(on: DispatchQueue.global(qos: .userInitiated))
.eraseToAnyPublisher()
}
}
Throwing from inside the guard causes the error Main actor-isolated property 'foo' can not be referenced from a non-isolated context
. However, if I just return nothing from the guard statement, it doesn't do this. I need to be able to throw, but I don't understand why it's mentioning the main actor stuff, when I've switched to receive on the main runloop?
CodePudding user response:
First, the compiler doesn't know that you've switched to the main thread, you do that at runtime, but the compiler doesn't know that when it compiles your code.
Second, the MainActor
and the main thread are two different things. The MainActor
guarantees that, when it is running code, the thread it is running that code on will be the only thread running MainActor
code. That does not guarantee your code will run on the main thread. But if it's running on another thread, the main thread will be suspended.
Finally the complaint you are seeing from the compiler is not that you're code is not on the main thread, but that you are using async
code in a non async
context. Because FooManager
is (apparently) actor isolated (which actor is immaterial), any calls to it must be await
ed and you can only use await
in an asynchronous context. Here the closure you pass to tryMap
is a synchronous context, it's not allowed to suspend, so the compiler complains.