Why must I specify @MainActor on init() but not on a function?

When trying to use a Task inside the init method of a class derived from NSWindowController, I have to explicitly annotate the initializer with @MainActor otherwise the compiler will complain if I attempt to call another property that is designated on the main actor.

However, I don't have to explicitly do this in a function on the class. Only the initializer. I assume this has to do with how @MainActor is specified on AppKit classes but I couldn't find any authoritative posts on that.

Consider:

import AppKit

final class WindowController: NSWindowController {

  let label = NSTextField()

  // This annotation is required otherwise the following error is reported: 
  // Property 'stringValue' isolated to global actor 'MainActor' can not 
  // be mutated from a non-isolated context.

  @MainActor
  init() {
    super.init(window: nil)

    Task {
      label.stringValue = "Hello World"
    }
  }

  // However, no such annotation is required here. Why is that?

  func updateLabel() {
    Task {
      label.stringValue = "Hello World"
    }
  }
}

Note that annotating the entire class with @MainActor does not seem to make a difference.

Are there any special rules for which async context I'm in from within a classes init method. What about for AppKit instances in particular?

SE-0316 briefly touches upon this under the section Using global actors on functions and data but in that example @MainActor is actually applied to every property and function.

1 Like

You should also be able to avoid the error by making the Task closure itself @MainActor:

init() {
    super.init(window: nil)

    Task { @MainActor in
      label.stringValue = "Hello World"
    }
  }

As for the disparity, Iā€™d recommend filing a bug: https://bugs.swift.org/

Indeed, though I'm curious why it's needed at all. In AppKit, NSResponder is annotated with @MainActor and NSWindowController inherits from NSResponder.

In SE-0316 it states:

Subclasses infer actor isolation from their superclass

That seems to be the case for a function but I'm curious if initializers follow different rules.

1 Like