我是否可以使用SWIFT中的参与者始终调用主线程上的函数? [英] Can I use actors in Swift to always call a function on the main thread?

查看:0
本文介绍了我是否可以使用SWIFT中的参与者始终调用主线程上的函数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我最近看到SWIFT在Swift 5.5中引入了对Actor模型的并发支持。此模型使安全并发代码能够在我们具有共享、可变状态时避免数据争用。

我希望在我的应用程序的用户界面中避免主线程数据竞争。为此,在我设置UIImageView.image属性或UIButton样式的任何地方,我都在调用点处包装DispatchQueue.main.async

// Original function
func setImage(thumbnailName: String) {
    myImageView.image = UIImage(named: thumbnailName)
}

// Call site
DispatchQueue.main.async {
    myVC.setImage(thumbnailName: "thumbnail")
}

这似乎不安全,因为我必须记住在主队列上手动分派该方法。另一个解决方案如下所示:

func setImage(thumbnailName: String) {
   DispatchQueue.main.async {
      myImageView.image = UIImage(named: thumbnailName)
   }
}

但这看起来像是很多样板,我不会说我喜欢将其用于具有多层嵌套的复杂函数。

SWIFT对Actors的支持的发布看起来是一个完美的解决方案。那么,有没有办法让我的代码更安全,也就是始终使用Actor调用主线程上的UI函数?

推荐答案

参与者隔离和重入现已在SWIFT标准库中实现。因此,苹果建议将该模型用于具有许多新的并发功能的并发逻辑,以避免数据竞争。我们现在有了一个更干净的替代方案,而不是基于锁的同步(许多样板)。

包括UIViewControllerUILabel在内的一些UIKit类现在具有对@MainActor的开箱即用支持。因此,我们只需要在定制的与UI相关的类中使用注释。例如,在上面的代码中,myImageView.image将自动在主队列上调度。但是,UIImage.init(named:)调用不会在视图控制器外部的主线程上自动调度。

一般情况下,@MainActor对于并发访问与UI相关的状态很有用,而且是最容易做的,尽管我们也可以手动调度。我在下面概述了可能的解决方案:

解决方案%1

尽可能简单。此属性在与UI相关的类中可能很有用。Apple使用@MainActor方法注释使该过程更加清晰:

@MainActor func setImage(thumbnailName: String) {
    myImageView.image = UIImage(image: thumbnailName)
}

此代码相当于包装DispatchQueue.main.async,但调用点现在是:

await setImage(thumbnailName: "thumbnail")

解决方案2

如果您有与自定义UI相关的类,我们可以考虑将@MainActor应用到类型本身。这确保了所有方法和属性都在MainDispatchQueue上调度。

然后,我们可以使用非UI逻辑的关键字nonisolated手动选择退出主线程。

@MainActor class ListViewModel: ObservableObject {
    func onButtonTap(...) { ... }

    nonisolated func fetchLatestAndDisplay() async { ... }
}

actor内调用onButtonTap时,不需要显式指定await

解决方案3(适用于块和函数)

我们还可以使用:

actor外部调用主线程上的函数
func onButtonTap(...) async {
    await MainActor.run { 
        ....
    }
}

在不同的actor中:

func onButtonTap(...) {
    await MainActor.run { 
        ....
    }
}

如果我们想从MainActor.run内返回,只需在签名中指定:

func onButtonTap(...) async -> Int {
    let result = await MainActor.run { () -> Int in
        return 3012
    }
    return result
}

此解决方案比上面的两个解决方案干净一些,这两个解决方案最适合在MainActor上包装整个函数。但是,actor.run还允许在一个func中的actor之间使用线程间代码(thx@Bill表示建议)。

解决方案4(在非异步函数中工作的块解决方案)

在解决方案3的@MainActor上计划块的替代方法:

func onButtonTap(...) {
    Task { @MainActor in
        ....
    }
}
与解决方案3相比,这里的优势在于封闭的func不需要标记为async。但是,请注意,这会在以后调度块,而不是像解决方案3中那样立即调度

摘要

参与者使SWIFT代码更安全、更干净、更易于编写。不要过度使用它们,但将UI代码分派到主线程是一个很好的用例。请注意,由于该功能仍处于测试阶段,因此该框架可能会在未来进一步更改/改进。

奖金备注

由于我们可以很容易地将actor关键字与classstruct互换使用,因此我建议将关键字限制在严格需要并发的情况下。使用关键字会增加实例创建的额外开销,因此在没有要管理的共享状态时没有意义。

如果您不需要共享状态,则不要不必要地创建它。struct实例创建非常轻量级,因此大多数情况下最好是创建一个新实例。例如SwiftUI

这篇关于我是否可以使用SWIFT中的参与者始终调用主线程上的函数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆