Why does a @MainActor annotated class initialisation gives the error
Asked Answered
W

1

6

I have a class named Person, Which is annotated with @MainActor. But as I try to create an instance of this mainactor class it gives an error.

"Call to main actor-isolated initializer 'init(firstName:lastName:)' in a synchronous nonisolated context".

@MainActor
class Person {
    var firstName: String = ""
    var lastName: String = ""
    
    init(firstName: String, lastName: String) {
        self.firstName = firstName
        self.lastName = lastName
    }
    
    func tryToPrintNameOnMainThread() {
        print("Is Main Thread: \(Thread.isMainThread)")
        print("\(firstName) \(lastName)")
    }
}

class AsyncAwaitViewModel {
     let personActor = Person(firstName: "", lastName: "") // Error
     let someTestLabel = UILabel() // No Error 
}


     

But if I create UILabel() instance it works. Where UILabel is also a @MainActor annotated class.

My question is why i am seeing this error and what should be ideal way to have @MainActor class instance in this type of scenario.

Creating the Person instance from @MainActor annotated UIViewController subclass works fine.

Weinhardt answered 21/12, 2022 at 10:36 Comment(4)
@Rob this is just a dummy learning model class person could be any thing, I was just trying to imitate the behaviour of '@ MainActor' annotated class. Where it is said that a type, property annotated with @ MainActor will route the all the operations on it thorough the await MainActor. run {} but i was not able to see that. Initially Person was just a @ MainActor var personA = Person(firstName: "", lastName: "") property.Weinhardt
If it is right to ask this in a separate question i will ask it there. As per this article **What that means is that, when using Swift’s new concurrency system, all properties and methods on those classes (and any of their subclasses, including our ProfileViewController) will automatically be set, called, and accessed on the main queue. All those calls will automatically be routed through the system-provided MainActor, which always performs all of its work on the main thread. ** but if. i try to update person's fName - errorsWeinhardt
Thank you the explanation. I do agree that an actor type should be right choice for a mutable thread safe type. But i was trying to understand why@MainActor annotated type or property did not route through await MainActor.run(body: { label.text = "dada" }). Is the article i linked is saying otherwise. Thanks you for patiently going through my queries.Weinhardt
Let us continue this discussion in chat.Radbun
A
1

Your isolated initialiser needs to be called from within an "async context". That is, call it from within a function being async, i.e. func f() async -> { ... }, or from within a Swift task: Task { ... }.

In your scenario, it would be better though to make the model the actor, and use a struct for your Person. If Person is an "Entity", which is used as a "DTO" send to a network API or CoreData, this makes much sense.

The model actor would then ensure thread-safety when mutating the Person value. When done with it, it would send the Person value to some service, for example. Since the Person value would not be mutated along the way, it doesn't need to be an actor.

Amorist answered 21/12, 2022 at 10:50 Comment(5)
If i understand your explanation. var x: Person? //= Person(firstName: "", lastName: "") let label: UILabel = UILabel() init() { Task { x = await Person(firstName: "", lastName: "") } } Can i do it from init() {} and Task {} inside it.Weinhardt
Also, please explain why UILabel does not show this error.Weinhardt
Basically, you need to await isolated functions from within an async context. It's pretty easy to get right. Notice that when calling (also: awaiting) an async function, that caller needs to be async as well. The compiler is really a great help and will warn you. But I am a bit reluctant to say just "yes" to your comment, because you should better not make Person an actor. ;)Amorist
"why UILabel does not show this error", this is actually a good question. IMHO it should yield the same warnings/errors. You may find more information in this link, it's a bug: github.com/apple/swift/issues/57457 - but it should have been resolved already. So, this leaves some confusion.Amorist
For me, when I fixed the Person problem (by making it an actor rather than something isolated to the main actor), the compiler did report the problem with the UILabel. It looks like it stops checking for main actor isolation at the first error it hits. (This latter problem, of the UILabel not being correctly isolated to the main actor, was fixed by making the whole view model @MainActor ... but then, again, a view object, such as UILabel, doesn't really belong in a view model, anyway, so it is a moot question.)Radbun

© 2022 - 2025 — McMap. All rights reserved.