hiccLoghicc log by wccHipo Log

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

家乡的清晨

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

手动dispath到主线程#

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

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模式。

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#

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已经将UILabelUIViewController 装饰过了。

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

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

自定义UI class#

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

@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环境中。#

@MainActor class ListViewModel: ObservableObject {
    ...

    func load() {
        loader.loadItems { [weak self] result in
    self?.result = result
}
    }
}
👉@MainActor 不会对Callback中的代码生效

@MainActor 必须使用在Swift async/await Concurrency环境中,否则无法生效。因为actor必须通过同步方式来获取。

参考自: