前言
在我们的项目开发过程中,经常会碰到多层闭包嵌套的情况,如果闭包又涉及到网络接口的调用,那我们需要时刻谨记对于回调中关于视图页面相关的操作要在主线程调用。
但不幸的是,这很容易被忽略掉,我就经常忘记回到主线程。咔咔写完一运行,项目崩溃了,一看原因才想起来没有回到主线程。😂
现在我们不需要时刻想着这事了,因为官方替我们解了忧。它就是本文的主题:MainActor
。
MainActor 简介
MainActor 是 Swift 5.5 中引入的一个新特性。它是一个全局actor,提供了一个在主线程上执行任务的执行器。使用 @MainActor
可以确保你的视图页面的相关代码总是在主线程上调用。
下面是 MainActor 的源码:
swift
@globalActor
final actor MainActor: GlobalActor {
static let shared: MainActor
}
首先,我们先来了解一下 @globalActor。下面是其源码:
csharp
public protocol GlobalActor {
associatedtype ActorType : Actor
global actor type.
static var shared: Self.ActorType { get }
}
通过代码可以看到,它是一个协议,如果我们遵守该协议,需要实现一个单例的静态属性。比如下面的这个例子:
scss
@globalActor
actor CustomActor { // 自定义的 actor
static let shared = CustomActor()
}
单例常量 shared
确保全局只有唯一一个实例。一旦定义,你就可以在整个项目中使用全局 actor,就像使用其他 actor 一样:
kotlin
@CustomActor
class CustomClass { }
MainActor 如何使用
MainActor 可以修饰属性、函数、闭包和实例变量。假设,对某个函数的调用我们想始终放在主线程,就可以像下面这么用:
less
@MainActor
func updateUIOperation() {
// 进行 UI 操作
}
除了使用 MainActor 修饰,我们也可以直接使用其 extension 中提供的 run 函数来进行使用:
less
extension MainActor {
public static func run<T>(resultType: T.Type = T.self, body: @MainActor @Sendable () throws -> T) async rethrows -> T where T : Sendable
}
这允许我们直接在函数中使用 MainActor,即使我们没有使用全局 actor 来修饰任何代码:
arduino
Task {
await fetchListData()
await MainActor.run {
// 进行 UI 操作
}
}
所以,以后我们就没必要再使用 DispatchQueue.main.async
来进行队列切换了。对于 MainActor 的这两种使用方式,我个人比较推荐第一种,因为对于第二种调用 run 函数这种方式,很可能会写着写着就忘了,从而发生视图页面的更新代码在后台线程执行的情况。
使用 MainActor 重构以前的代码
在 Swift 5.5 之前,如果编写队列切换的代码,你可能会使用下面这种方式:
less
func fetchImage(for url: URL, completion: @escaping (Result<UIImage, Error>) -> Void) {
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data, let image = UIImage(data: data) else {
DispatchQueue.main.async {
completion(.failure(ImageFetchingError.imageDecodingFailed))
}
return
}
DispatchQueue.main.async {
completion(.success(image))
}
}.resume()
}
在上面的示例中,如果请求失败,我们需要回到主线程将错误返回,如果请求成功我们也需要回到主线程返回 image。这就导致了几个闭包的代码混乱。
通过使用 MainActor 和 async/await 的搭配,我们可以将上面的例子重构成下面的代码:
swift
@MainActor
func fetchImage(for url: URL) async throws -> UIImage {
let (data, _) = try await URLSession.shared.data(from: url)
guard let image = UIImage(data: data) else {
throw ImageFetchingError.imageDecodingFailed
}
return image
}
@MainActor 确保逻辑在主线程上执行,而网络请求仍然在后台队列上执行。只有在需要确保最佳性能时才会调度到 MainActor。