---
title: "使用@MainActor自动在主线程更新UI"
description: "Swift 5.5之后，直接使用@MainActor保证UI更新代码在主线程运行，不再需要重复使用 DispatchQueue.main.async 方式。"
url: "https://hicc.pro/p/swift/the-main-actor-attribute"
author: "tyler wang"
published: "2021-07-17T05:56:10.000Z"
updated: "2021-07-17T11:07:35.000Z"
category: "Swift"
image: "https://image.hicc.pro/pro/20210717135808Fg5EC7lqGwj-vnywK1OaO3xH0W5uahf-x1xqC_QTzZnFDBP_q.jpg"
lang: "zh-CN"
site: "Hipo日志"
---

# 使用@MainActor自动在主线程更新UI

![家乡的清晨](https://image.hicc.pro/pro/20210717175842Fg5EC7lqGwj-vnywK1OaO3xH0W5uwivWdYRr58mjGU3GA7Gby.jpg)

*家乡的清晨*

Swift 5.5 终于为开发者带来了async，await，actor这些便捷的异步语法，而其中一个小小的@MainActor语法，能带来让我们的开发更加便捷安全。

### 手动dispath到主线程

在swift 5.5 之前，我们需要手动使用`DispatchQueue.main`来让代码运行在主线程，特别是UI更新操作。这样没问题，但是略显麻烦，而且容易遗漏。

```swift
class ProfileViewController: UIViewController {
    private let userID: User.ID
    private let loader: UserLoader
    private lazy var nameLabel = UILabel()
    private lazy var biographyLabel = UILabel()
    ...

    private func loadUser() {
        loader.loadUser { [weak self] result in
            DispatchQueue.main.async {
                switch result {
                case .success(let user):
                    self?.nameLabel.text = user.name
                    self?.biographyLabel.text = user.biography
                case .failure(let error):
                    self?.showError(error)
                }
            }
        }
    }
}
```

### @MainActor

Swift 5.5 内置了的`actor`，MainActor被装饰的操作自动运行在主线程。

首先我们需要将我们的异步callback代码，转换成async/await模式。

```swift
extension UserLoader {
    func loadUser() async throws -> User {
        try await withCheckedThrowingContinuation { continuation in
            loadUser { result in
                switch result {
                case .success(let user):
                    continuation.resume(returning: user)
                case .failure(let error):
                    continuation.resume(throwing: error)
                }
            }
        }
    }
}
```

#### 在UIKit中使用@MainActor

```swift
class ProfileViewController: UIViewController {
    ...
    
    private func loadUser() {
        async {
            do {
                let user = try await loader.loadUser()
                nameLabel.text = user.name
                biographyLabel.text = user.biography
            } catch {
                showError(error)
            }
        }
    }
}
```

等等，没有看到`@MainAcotr`?

那是因为apple已经将`UILabel`和`UIViewController` 装饰过了。

```swift
@MainActor class UILabel: UIView
@MainActor class UIViewController: UIResponder
```

也就是，**在swift 的concurrency 系统中，被`@MainActor`装饰过的类，及其子类的属性和方法，都会自动在主线程中，get，set，或者call**。

#### 自定义UI class

假设，我们SwiftUI中的一个实现`ObservableObject`的类，其中被`@Published`装饰的属性需要自动运行在主线程。 我们只需要装饰`@MainActor`即可。

```swift
@MainActor class ListViewModel: ObservableObject {
    @Published private(set) var result: Result<[Item], Error>?
    private let loader: ItemLoader
    ...

    func load() {
        async {
            do {
                let items = try await loader.loadItems()
                result = .success(items)
            } catch {
                result = .failure(error)
            }
        }
    }
}
```

就是这么简单！！！

#### @MainActor 只能运行在async/await环境中。

```swift
@MainActor class ListViewModel: ObservableObject {
    ...

    func load() {
        loader.loadItems { [weak self] result in
    self?.result = result
}
    }
}
```

> **@MainActor 不会对Callback中的代码生效**
> @MainActor 必须使用在Swift async/await Concurrency环境中，否则无法生效。因为actor必须通过同步方式来获取。

---

参考自：

[Using the MainActor attribute to automatically dispatch UI updates on the main queue | Swift by Sundell](https://www.swiftbysundell.com/articles/the-main-actor-attribute/): How the MainActor attribute eliminates the need for us to manually dispatch UI updates on the main queue when using Swift 5.5’s new concurrency system.

---

原文 / Source: <https://hicc.pro/p/swift/the-main-actor-attribute>
