前语

在咱们的项目开发过程中,常常会碰到多层闭包嵌套的状况,假如闭包又涉及到网络接口的调用,那咱们需求时间谨记关于回调中关于视图页面相关的操作要在主线程调用。

但不幸的是,这很简单被忽略掉,我就常常忘掉回到主线程。咔咔写完一运行,项目溃散了,一看原因才想起来没有回到主线程。

现在咱们不需求时间想着这事了,因为官方替咱们解了忧。它就是本文的主题:MainActor

MainActor 简介

MainActor 是 Swift 5.5 中引进的一个新特性。它是一个大局actor,供给了一个在主线程上履行任务的履行器。运用 @MainActor能够保证你的视图页面的相关代码总是在主线程上调用。

下面是 MainActor 的源码:

@globalActor
final actor MainActor: GlobalActor {
    static let shared: MainActor 
}

首先,咱们先来了解一下 @globalActor。下面是其源码:

public protocol GlobalActor {
    associatedtype ActorType : Actor
    global actor type.
    static var shared: Self.ActorType { get }
}

经过代码能够看到,它是一个协议,假如咱们恪守该协议,需求完成一个单例的静态特点。比方下面的这个比如:

@globalActor
actor CustomActor { // 自界说的 actor
    static let shared = CustomActor()
}

单例常量 shared 保证大局只有唯一一个实例。一旦界说,你就能够在整个项目中运用大局 actor,就像运用其他 actor 一样:

@CustomActor
class CustomClass { }

MainActor 怎么运用

MainActor 能够润饰特点、函数、闭包和实例变量。假设,对某个函数的调用咱们想始终放在主线程,就能够像下面这么用:

@MainActor
func updateUIOperation() {
    // 进行 UI 操作
}

除了运用 MainActor 润饰,咱们也能够直接运用其 extension 中供给的 run 函数来进行运用:

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 来润饰任何代码:

Task {
    await fetchListData()
    await MainActor.run {
        // 进行 UI 操作
    }
}

所以,以后咱们就没必要再运用 DispatchQueue.main.async 来进行行列切换了。关于 MainActor 的这两种运用方法,我个人比较推荐第一种,因为关于第二种调用 run 函数这种方法,很可能会写着写着就忘了,然后发生视图页面的更新代码在后台线程履行的状况。

运用 MainActor 重构以前的代码

在 Swift 5.5 之前,假如编写行列切换的代码,你可能会运用下面这种方法:

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 的搭配,咱们能够将上面的比如重构成下面的代码:

@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。