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
- 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
}
- 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
}
}
- 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:
- Access the property from an asynchronous block with
@MainActor
context. - 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
Vertically Aligning Text in an Nstextfield Using Swift
How to Find Actual Swiftui API Documentation (And Not Just the Developer Documentation)
Know When an Iteration Over Array with Async Method Is Finished
Why Can't You Assign an Optional to a Variable of Type 'Any' Without a Warning
Reading Currently Playing Track in MACos Using Scriptingbridge Not Working
Make a Uibarbuttonitem Disappear Using Swift iOS
How to Create a String from Utf8 in Swift
Rxswift Merge Different Kind of Observables
Why Can't We Use Protocol 'Encodable' as a Type in the Func
How Is Optional Binding Used in Swift
How to Get Random Element from a Set in Swift
Why I Can Change/Reassigned a Constant Value That Instantiated from a Class
Unexpectedly Large Realm File Size
Division Not Working Properly in Swift
Swift Closure Not Setting Variable
Cannot Invoke 'Join' with an Argument List of Type (String, [String]) in Swift 2.0