2

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.

the monk
  • 389
  • 4
  • 14
  • @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. – the monk Dec 22 '22 at 05:21
  • If it is right to ask this in a separate question i will ask it there. As per [this](https://www.swiftbysundell.com/articles/the-main-actor-attribute/) 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 - errors – the monk Dec 22 '22 at 05:34
  • 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. – the monk Dec 22 '22 at 07:54
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/250599/discussion-between-rob-and-the-monk). – Rob Dec 22 '22 at 08:14

1 Answers1

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.

CouchDeveloper
  • 18,174
  • 3
  • 45
  • 67
  • 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. – the monk Dec 21 '22 at 10:59
  • Also, please explain why UILabel does not show this error. – the monk Dec 21 '22 at 11:06
  • 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. ;) – CouchDeveloper Dec 21 '22 at 11:06
  • 1
    "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: https://github.com/apple/swift/issues/57457 - but it should have been resolved already. So, this leaves some confusion. – CouchDeveloper Dec 21 '22 at 11:31
  • 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.) – Rob Dec 21 '22 at 21:40