How to Fix "Expression Requiring Global Actor 'Mainactor' Cannot Appear in Default-Value Expression of Property '_Audioplaybackmanager'"

Swift 6 Error: Expression requiring global actor 'MainActor' cannot appear in default-value expression of property '_api'

That's a warning in Swift 5. In Swift 6, it'll be an error. It's suggesting that you fix it now, but in the future it'll break the build. You're requiring that everything (including the init be run on the MainActor, but this assignment isn't promised to run on the MainActor itself (possibly because the type it's part of isn't also marked @MainActor). You may find a lot of these in 5.6 that are extremely difficult to fix. They're still tuning the warnings. (So when you start to formulate your next question "how do I eliminate all Swift concurrency warnings when dealing with SwiftUI and Foundation," the answer may be "you can't, or at least not in any easy way." It depends on the exact problem.)

SwiftUI @MainActor loses global actor

See my comment on the question about whether or not the @MainActor strategy is the right way to address an underlying issue, but to directly address your compilation error, you can use this syntax, which compiles fine:

.onChange(of: viewModel.username) { viewModel.editingChanged($0) }

SwiftUI - Best pattern to simplify a view init() that's the same across different views

Well, actually it is possible (I don't know all your 20+ views, but still) to try using generics to separate common parts and generalise them via protocols and dependent views.

Here is a simplified demo of generalisation based on your provided snapshot. Tested with Xcode 13.2 / iOS 15.2

Note: as you will see the result is more generic, but it seems you will need more changes to adapt it than you would just change inits

  1. Separate model into protocol with associated type and required members
protocol LoaderInterface: ObservableObject {  // observable
associatedtype Parser // associated parser
init() // needed to be creatable

var isLoading: Bool { get } // just for demo
}

  1. Generalize a view with dependent model and builder based on that model
struct LoadingView<Loader, Content>: View where Loader: LoaderInterface, Content: View {

@StateObject private var loader: Loader
private var content: (Loader) -> Content

init(@ViewBuilder content: @escaping (Loader) -> Content) {
self._loader = StateObject(wrappedValue: Loader())
self.content = content
}

var body: some View {
content(loader) // build content with loader inline
// so observing got worked
}
}

  1. Now try to use above to create concrete view based on concrete model
protocol Creatable {    // just helper
init()
}


// another generic loader (as you would probably already has)
class MyLoader<T>: LoaderInterface where T: Creatable {
typealias Parser = T // confirm to LoaderInterface

var isLoading = false

private var parser: T
required init() { // confirm to LoaderInterface
parser = T()
}
}

class MyParser: Creatable {
required init() {} // confirm to Creatable
func parse() {}
}

// demo for specified `LoadingView<MyLoader<MyParser>>`
struct LoaderDemoView: View {
var body: some View {
LoadingView { (loader: MyLoader<MyParser>) in
Text(loader.isLoading ? "Loading..." : "Completed")
}
}
}

Conforming @MainActor class, or actor, to Codable

There isn't anything about the class you've provided that needs to be isolated to the main actor, so don't isolate the class as a whole. If there are other members that you have not shown us that do need to be isolated to the main actor, isolate them.

Example:

final class MyClass: Codable {
private var value: Int
@MainActor init(value: Int) {
self.value = value
}
@MainActor func setMyValue(to newValue:Int) {
self.value = newValue
}
@MainActor func getMyValue() -> Int {
self.value
}
enum CodingKeys: String, CodingKey {
case value
}
init(from decoder: Decoder) throws {
let data = try decoder.container(keyedBy: CodingKeys.self)
self.value = try data.decode(Int.self, forKey: .value)
}
func encode(to encoder: Encoder) throws { // <-- Compiler error: Instance method 'encode(to:)' isolated to global actor 'MainActor' can not satisfy corresponding requirement from protocol 'Encodable'
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(value, forKey: .value)
}
}

Use only `await` instead `await MainActor.run { }` for @MainActor properties

Yes, the two snippets will behave the same.
The await will cause the current asynchronous call to "hop" the main actor and execute there.

The compiler (and Swift runtime) know that name can only be accessed from the main thread/actor, so will require you to either:

  1. Access the property from an asynchronous block with @MainActor context.
  2. Hop to the main actor from the current thread to execute there. Then, resume execution of the current function afterwards (however, the function could resume on a different thread than it was originally executing on prior to the hop to the main actor).

Your first snippet is essentially doing both of these steps, introducing a @MainActor scope to run your code in, then you are hopping to the main actor to execute it before continuing with your function call.
The second snippet is just skipping the scope creation part, running the single line of code on the main actor, then hopping back right away.

If you're running multiple things on the main actor you will want to reduce the number of hops that are performed, because this will introduce a lot of overhead if you are hopping back-and-forth to and from the main actor.
For example:

@MainActor func someUIOperation() async { ... }

func expensiveLotsOfHopsToMainActor() async {
for _ in 0..<100 {
await someUIOperation()
}
}

func betterOnlyOneHopToMainActor() async {
await MainActor.run {
for _ in 0..<100 {
someUIOperation()
}
}
}

See examples and more in the original pitch for @MainActor.

How do I initialize a global variable with @MainActor?

One way would be to store the variable within a container (like an enum acting as an abstract namespace) and also isolating this to the main actor.

@MainActor
enum Globals {
static var foo = Foo()
}

An equally valid way would be to have a "singleton-like" static property on the object itself, which serves the same purpose but without the additional object.

@MainActor
struct Foo {
static var shared = Foo()
}

You now access the global object via Foo.global.

One thing to note is that this will now be lazily initialized (on the first invocation) rather than immediately initialized.
You can however force an initialization early on by making any access to the object.

// somewhere early on
_ = Foo.shared


Bug in Swift 5.5, 5.6, 5.7

TL;DR: @MainActor sometimes won't call static let variables on the main thread. Use static var instead.

While this compiles and works, it appears that this may call the initializer off the main thread and subsequently any calls made inside the initializer.

@MainActor
func bar() {
print("bar is main", Thread.isMainThread)
}

@MainActor
struct Foo {
@MainActor
static let shared = Foo()

init() {
print("foo init is main", Thread.isMainThread)
bar()
}

func fooCall() {
print("foo call is main", Thread.isMainThread)
}
}
Task.detached {
await Foo.shared.fooCall()
}
// prints:
// foo init is main false
// bar is main false
// foo call is main true

This is a bug (see issue 58270).

A workaround is to always use a static var instead of a static let, as the correct isolation behaviour is enforced in that case.



Related Topics



Leave a reply



Submit