2025-02-07 10:14:35
,某些文章具有时效性,若有错误或已失效,请在下方留言。Swift tries to ensure access to shared data is done safely, partly through types such as actors, and partly through a concept of sendability implemented through the Sendable
protocol and the @Sendable
attribute. You’ll know you’ve hit this problem when you see the error, “Non-sendable type ‘YourType’ returned by implicitly asynchronous call to nonisolated function cannot cross actor boundary.”
Swift 试图确保对共享数据的访问是安全的,部分是通过 actor 等类型,部分是通过 Sendable
协议和 @Sendable
属性实现的可发送性 概念。当您看到错误“Non-sendable type ‘YourType’ returned by implicitly asynchronous call to nonisolated function cannot cross actor boundary.”时,您就会知道您遇到了此问题。
It’s quite a dense error, so let me simplify it for you: “you’re making an object here, and you want to use it somewhere else, but that isn’t safe.”
这是一个相当密集的错误,所以让我为你简化一下:“你在这里制作了一个对象,你想在其他地方使用它,但这并不安全。
You can see the error yourself with code like this:
你可以自己通过如下代码看到错误:
class User {
let name: String
let password: String
init(name: String, password: String) {
self.name = name
self.password = password
}
}
@main
struct App {
static func main() async {
async let user = User(name: "twostraws", password: "fr0st1es")
await print(user.name)
}
}
You can see that we have implicitly made a class be accessed on a separate task through our use of async let
, but you can also see that both the class’s properties are marked as constant, so actually it’s safe.
你可以看到,通过使用 async let
,我们隐式地使一个类可以在单独的任务上被访问,但你也可以看到,该类的两个属性都被标记为 constant,所以实际上它是安全的。
To fix the problem, we need to mark the class as being safe to send across different tasks in our app. This is done in two steps, starting with making the class conform to the Sendable
protocol, like this:
要解决此问题,我们需要将类标记为可以安全地发送到应用程序中的不同任务。这分两步完成,首先使类符合 Sendable
协议,如下所示:
class User: Sendable {
That tells Swift this class can be sent between tasks safely, which Swift validates for us – it will make sure all the properties are also Sendable
, otherwise it will refuse to build. In this case, the properties are constant value types, so they are safe to share.
这告诉 Swift 这个类可以在任务之间安全地发送,Swift 会为我们验证 —— 它将确保所有属性也是 Sendable
的,否则它将拒绝构建。在这种情况下,属性是常量值类型,因此可以安全地共享它们。
And secondly, we need to declare the class as being final
– saying that it can’t be subclassed – like this:
其次,我们需要将类声明为 final
—— 说它不能被子类化 —— 像这样:
final class User: Decodable, Sendable {
The final
part here is important: Swift can’t guarantee this class is definitely sendable unless it can’t be subclassed, just in case we make a subclass that adds a property that isn’t sendable, at which point things get rather messy.final
部分很重要:除非它不能被子类化,否则 Swift 无法保证这个类绝对是可发送的,以防万一我们创建一个子类,它添加了一个 不可 发送的属性,这时事情就会变得相当混乱。
So, the completed code is this:
所以,完成的代码是这样的:
final class User: Decodable, Sendable {
let name: String
let password: String
init(name: String, password: String) {
self.name = name
self.password = password
}
}
@main
struct App {
static func main() async {
async let user = User(name: "twostraws", password: "fr0st1es")
await print(user.name)
}
}
Swift automatically considers all actors as conforming to Sendable
because they automatically handle synchronization correctly. It will also automatically consider structs and enums as being Sendable
, as long as they only contain values that are also Sendable
.
Swift 会自动将所有 actor 视为符合 Sendable
,因为它们会自动正确处理同步。它还会自动将结构和枚举视为 Sendable
,只要它们仅包含也是 Sendable
的值。
Things are a little more complex when it comes to functions, because Swift needs to make sure the function or the type that owns the function can also be sent correctly.
当涉及到函数时,事情要复杂一些,因为 Swift 需要确保 函数或拥有 该函数的类型也可以被正确发送。
Let’s say we wanted to make a tiny wrapper around the old asyncAfter()
method from GCD, so we can execute a function after a short delay. We’d need to write code like this:
假设我们想为 GCD 中的旧 asyncAfter()
方法制作一个小的包装器,这样我们就可以在短暂的延迟后执行一个函数。我们需要编写这样的代码:
func runLater(_ function: @escaping @Sendable () -> Void) {
DispatchQueue.global().asyncAfter(deadline: .now() + 3, execute: function)
}
This is because asyncAfter()
only accepts functions that can safely be sent between tasks, to avoid data races. So, we need to pass that requirement up to whoever calls runLater()
, which is what the @Sendable
attribute is doing – we’re saying whatever function we’re told to run must be safe to transfer.
这是因为 asyncAfter()
只接受可以在任务之间安全发送的函数,以避免数据竞争。因此,我们需要将这个要求传递给runLater()
的调用者 ,这就是 @Sendable
属性的作用——我们说我们被告知运行的任何函数都必须是可以安全传输的。
The same is true for a great many other APIs that need to run their work elsewhere. For example, making a Timer
fire repeatedly to run an action again and again requires an @Sendable @escaping
function to run:
对于需要在其他地方运行工作的许多其他 API 来说,情况也是如此。例如,使 Timer
重复触发以一次又一次地运行作需要运行 @Sendable @escaping
函数:
func repeatAction(_ action: @escaping @Sendable () -> Void) {
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
action()
}
}
To put that to use, we could make a simple Counter
actor that was able to increment its value whenever we wanted. Actors are inherently Sendable
, so we can create a task to call our counter’s increment()
method and use that safely with repeatAction()
:
为了使用它,我们可以制作一个简单的 Counter
actor,它能够在我们想要的时候增加它的值。Actor 本质上是可发送
的,因此我们可以创建一个任务来调用计数器的 increment()
方法,并将其安全地与 repeatAction()
一起使用:
func repeatAction(_ action: @escaping @Sendable () -> Void) {
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
action()
}
}
actor Counter {
var value = 0
func increment() {
value += 1
print("Counter incremented: \(value)")
}
}
let counter = Counter()
repeatAction {
Task {
// This is safe
await counter.increment()
}
}
// Make sure the timer has chance to fire a few times.
try? await Task.sleep(for: .seconds(5))
So, the Task
is Sendable
if its work is also Sendable
.
因此,如果 Task
的工作也是 Sendable
的,则该 Task 是 Sendable
的。
暂无评论内容