You create your Task at the top level, which implicitly makes it run on the main actor. Since Base is just a class (not an actor), its methods are run in whatever context its callers are in, which is the main actor in this case.
You don't have to. Properties and methods can individually be associated with specific actors, including as part of protocol requirements. In this case foo is implicitly @MainActor because the Po protocol declares it as such.
It is perhaps a little too magical - foo is also implicitly async even though it's never actually marked as such, even in the original protocol declaration.
To test this further, if you add to Base e.g.:
func bar() {
foo()
}
…you'll get the compiler error Call to main actor-isolated instance method 'foo()' in a synchronous nonisolated context, demonstrating that the compiler is treating foo as @MainActor (but that the rest of Base isn't).
Yes, I know foo is on MainActor.
But class Base it isn’t on the MainActor.
So, I would expect str = “xxx” in foo would show an error to me. Because they are on different thread. As my understanding. I may be wrong. Please correct me.