2025-02-11 10:11:40
,某些文章具有时效性,若有错误或已失效,请在下方留言。@MainActor
is a global actor that uses the main queue for executing its work. In practice, this means methods or types marked with @MainActor
can (for the most part) safely modify the UI because it will always be running on the main queue, and calling MainActor.run()
will push some custom work of your choosing to the main actor, and thus to the main queue.@MainActor
是一个全局 actor,它使用主队列来执行其工作。@MainActor
的方法或类型可以(在大多数情况下)安全地修改 UI,因为它将始终在主队列上运行,并且调用 MainActor.run()
会将您选择的一些自定义工作推送到主 actor,,从而推送到主队列。
At the simplest level both of these features are straightforward to use, but as you’ll see there’s a lot of complexity behind them.
在最简单的层面上,这两个功能都很容易使用,但正如您将看到的,它们背后有很多复杂性。
First, let’s look at using @MainActor
, which automatically makes a single method or all methods on a type run on the main actor. This is particularly useful for any types that exist to update your user interface, such as @Observable
or ObservableObject
classes.
首先,让我们看看使用 @MainActor
,它会自动使类型上的单个方法或所有方法在主 actor 上运行。这对于用于更新用户界面的任何类型的 @Observable
或 ObservableObject
类特别有用。
For example, we could create an @Observable
class with two properties, and because they will both update the UI we would mark the whole class with @MainActor
to ensure these UI updates always happen on the main actor no matter where they are triggered from:
例如,我们可以创建一个具有两个属性的 @Observable
类,由于它们都将更新 UI,因此我们将用 @MainActor
标记整个类,以确保这些 UI 更新始终发生在主 actor 上,无论它们从何处触发:
@Observable @MainActor
class AccountViewModel {
var username = "Anonymous"
var isAuthenticated = false
}
Or if you were using the old ObservableObject
protocol, it would be this:
或者,如果您使用的是旧的 ObservableObject
协议,则为:
@MainActor
class AccountViewModel: ObservableObject {
@Published var username = "Anonymous"
@Published var isAuthenticated = false
}
Now, in Xcode 16 and later SwiftUI was updated so that all structs that conform to View
are automatically and entirely run on the main actor, which makes all this work a great deal easier. This is not attached to your deployment target – any iOS you compile using Xcode 16 or later benefits from this change.
现在,在 Xcode 16 及更高版本中,SwiftUI 进行了更新,以便所有符合 View
的结构都自动且完全在主 actor上运行,这使得所有这些工作都变得更加容易。这不会 附加到您的部署目标 – 您使用 Xcode 16 或更高版本编译的任何 iOS 都将从此更改中受益。
Does that mean you don’t need to explicitly add @MainActor
to your observable classes? Well, no – there are still benefits to using @MainActor
with these classes, not least if they are using async
/await
to do their own asynchronous work such as downloading data from a server.
这是否意味着您不需要将 @MainActor
显式添加到 observable 类中?嗯,不 —— 将 @MainActor
与这些类一起使用仍然有好处,尤其是当它们使用 async
/await
来执行自己的异步工作(例如从服务器下载数据)时。
So, my recommendation is simple: even though SwiftUI forces main-actor-ness for all its views, it’s still a good idea to add the @MainActor
attribute to all your observable classes to be absolutely sure all UI updates happen on the main actor. If you need certain methods or computed properties to opt out of running on the main actor, use nonisolated
as you would do with a regular actor.
所以,我的建议很简单:@MainActor
属性添加到所有可观察类中仍然是一个好主意,以绝对确保所有 UI 更新都发生在 main actor 上。nonisolated
。
Important: I’ve said it previously, but it’s worth repeating: you should not attempt to use actors for your observable objects, because we must do all UI updates on the main actor rather than a custom actor.
重要: 我之前已经说过,但值得重复一遍:您不应该 尝试将 actor 用于 observable 对象,因为我们必须在主 actor 上执行所有 UI 更新,而不是在自定义 actor 上执行。
The magic of @MainActor
is that it automatically forces methods or whole types to run on the main actor, a lot of the time without any further work from us. Previously we needed to do it by hand, remembering to use code like DispatchQueue.main.async()
or similar every place it was needed, but now the compiler does it for us automatically.@MainActor
的神奇之处在于,它会自动强制方法或整个类型在主 actor 上运行,很多时候我们不需要我们做任何进一步的工作。以前我们需要手动完成,记住在需要的地方使用 DispatchQueue.main.async()
或类似的代码,但现在编译器会自动为我们完成。
Be careful: @MainActor
is really helpful to make code run on the main actor, but it’s not foolproof. For example, if you have a @MainActor
class then in theory all its methods will run on the main actor, but one of those methods could trigger code to run on a background task. For example, if you’re using Face ID and call evaluatePolicy()
to authenticate the user, the completion handler will be called on a background thread even though that code is still within the @MainActor
class.
请注意:@MainActor
确实有助于使代码在 main actor 上运行,但并非 万无一失。例如,如果您有一个 @MainActor
类,那么理论上它的所有方法都将在主 actor 上运行,但其中一个方法可以触发代码在后台任务上运行。例如,如果使用 Face ID 并调用 evaluatePolicy()
对用户进行身份验证,则将在后台线程上调用完成处理程序,即使该代码仍在 @MainActor
类中。
If you do need to spontaneously run some code on the main actor, you can do that by calling MainActor.run()
and providing your work. This allows you to safely push work onto the main actor no matter where your code is currently running, like this:
如果您确实 需要在 main actor 上自发地运行一些代码,您可以通过调用 MainActor.run()
并提供您的工作来实现。这样,无论您的代码当前在何处运行,您都可以安全地将工作推送到 main actor 上,如下所示:
func couldBeAnywhere() async {
await MainActor.run {
print("This is on the main actor.")
}
}
await couldBeAnywhere()
You can send back nothing from run()
if you want, or send back a value like this:
如果需要,你可以从 run()
返回任何内容,或者发送回一个像这样的值:
func couldBeAnywhere() async {
let result = await MainActor.run {
print("This is on the main actor.")
return 42
}
print(result)
}
await couldBeAnywhere()
Even better, if that code was already running on the main actor then the code is executed immediately – it won’t wait until the next run loop in the same way that DispatchQueue.main.async()
would have done.
更好的是,如果该代码已经在主 actor 上运行,那么代码会立即执行 – 它不会像 DispatchQueue.main.async()
那样等到下一个运行循环。
If you wanted the work to be sent off to the main actor without waiting for its result to come back, you can place it in a new task like this:
如果您希望将工作发送给 main actor 而不 等待其结果返回,您可以将其放在新任务中,如下所示:
func couldBeAnywhere() {
Task {
await MainActor.run {
print("This is on the main actor.")
}
}
// more work you want to do
}
couldBeAnywhere()
Or you can also mark your task’s closure as being @MainActor
, like this:
或者,您也可以将任务的 closure 标记为 @MainActor
,如下所示:
func couldBeAnywhere() {
Task { @MainActor in
print("This is on the main actor.")
}
// more work you want to do
}
couldBeAnywhere()
This is particularly helpful when you’re inside a synchronous context, so you need to push work to the main actor without using the await
keyword.
当您在同步上下文中时,这特别有用,因此您需要在不使用 await
关键字的情况下将工作推送到主 actor。
Important: If your function is already running on the main actor, using await MainActor.run()
will run your code immediately without waiting for the next run loop, but using Task
as shown above will wait for the next run loop.
重要: 如果您的函数已在主 actor 上运行,则使用 await MainActor.run()
将立即运行您的代码,而无需等待下一个运行循环,但使用上所示的 Task
将 等待下一个运行循环。
You can see this in action in the following snippet:
您可以在以下代码片段中看到这一点:
@MainActor @Observable
class ViewModel {
func runTest() async {
print("1")
await MainActor.run {
print("2")
Task { @MainActor in
print("3")
}
print("4")
}
print("5")
}
}
let model = ViewModel()
await model.runTest()
try await Task.sleep(for: .seconds(0.1))
That marks the whole type as using the main actor, so the call to MainActor.run()
will run immediately when runTest()
is called. However, the inner Task
will not run immediately, so the code will print 1, 2, 4, 5, 3.
这会将整个类型标记为使用主 actor,因此在调用 runTest()
时,对 MainActor.run()
的调用将立即运行。但是,内部 Task
不会 立即运行,因此代码将打印 1、2、4、5、3。
Although it’s possible to create your own global actors, I think you should probably avoid doing so until you’ve had sufficient chance to build apps using what you already have.
尽管可以创建自己的全局 actor,但我认为在你有足够的机会使用已有的资源构建应用程序之前,你可能应该避免这样做。