原文:www.massicotte.org/crossing-th…
作者:Matt
使用Swift并发时会遇到很多棘手的问题,其中之一就是处理不可发送类型。它们到处都有。当然,把一个类型变成可发送类型有时候很容易。但是,当这样做不容易的时候,我们来看看你能做什么。
注意:我将要讨论的大部分问题都是在Swift 并发相关警告打开的情况下出现的。没有它们,我们不能正确的使用Swift 并发,所以你应该打开这些警告。
Actor 边界
因为我们假设你不能把这些类型变成可发送类型,所以你只有一个选择。你只能在一个单一Actor 上下文中使用这些值。我们不能跨越那些actor隔离边界。
我将给出四个选择,但是毫无疑问应该有更多选择。这些仅仅是我研究过的,我感觉它们很有用。
保持在 MainActor
通常,不可发送类型来自MainActor. 所以不要丢掉它。使用它有很多优势。你能够很容易的和UI 交互。毫无疑问,你当然也可以使用async保持UI 响应。并且,明智的人总是建议尽可能的保持在主线程。
我认为当你遇到了一个可发送类型的问题,努力想一下为什么必须在另一个 actor 中使用很有价值。对我而言,有时这就像是一个教条式的目标——必须把所有东西都移出主线程。关键在于,async/await 现在可以让你在不离开主线程的情况下,轻松实现高度异步的代码行为。
Actor 扩展
深入思考MainActor设计,对开发者具有指导意义。有成千上万的类型只能在该特定Actor上安全使用,其规模堪称庞大!诚然这是个特例,但我们仍可借鉴其设计原理。只需持续扩大Actor的作用域,直至无需再传递不可发送类型数据。其作用域已扩展至包含所有相关元素,我将此解决方案命名为:actor 扩展。
是的,MainActor使用这个到了极致。但是,你不必做到这种极端程度。你只需要增加actor的职责,使得足够解决不可发送类型的问题。有一个事情值得我们关注。这种职责的增加可能会损坏整体设计。当我把class类型转化成actor类型时,我发送这个问题很普遍。我认为,当您能够将并发问题封装为明确定义的功能模块时——正如MainActor所做的那样——Actor扩展模式才能发挥最佳效果。但该模式在其他场景中同样能发挥卓越效果。并且,像MainActor一样,这项技术和全局Actor配合使用效果极佳。虽非常规首选方案,但在特定场景下能发挥关键作用。
创建捕获
(Swift 6 编译器已经让这个技术完全没有必要了)
通常,我会遇到actor初始化问题。我想要让一个actor拥有不可发送类型,但是出于一些原因,让actor自身创建那些值非常麻烦。
actor MyActor {
init(_ nonSendable: NonSendable) {
// ....
}
}
let nonSendable = NonSendable()
// this isn't allowed!
let myActor = MyActor(nonSendable)
然而,这里有一个非常简单的方法来解决这个问题。
actor MyActor {
init(_ nonSendable: @Sendable () -> NonSendable) {
// ....
}
}
// this is fine!
let myActor = MyActor({ NonSendable() })
因为闭包是可发送类型,所以这里可行。记住:可发送闭包可以创建不可发送类型,它们只是不能捕获这种类型。该设计将不可发送类型的创建过程控制权交由调用方管理,实践证明此技术方案极具实用价值!
我们可以使用@autoclosure来简化API.
actor MyActor {
init(_ nonSendable: @autoclosure @Sendable () -> NonSendable) {
// ....
}
}
// hot-damn!
let myActor = MyActor(NonSendable())
欺骗
你已经尝试以上所有选择,但是你还是不能找到方法消除这些可发送类型的警告. 对我来说,使用没有被更新到Swift并发的苹果API时,我也会遇到这种情况。你可以采取以下任一方案。要么你先保留这些警告,或者欺骗编译器。后一种方案实现如下。
struct SendableBox<NonSendable>: @unchecked Sendable {
let value: NonSendable
init(_ value: NonSendable) {
self.value = value
}
}
let scareQuoteSendable = SendableBox(NonSendable())
必须极其谨慎地使用此类技巧。您必须确保所有权转移的方式绝对不可能引发数据竞争。需特别注意,此方案仅应作为最终手段使用。
结论
我在使用Swift并发编程的过程中遇到了诸多挑战。在这个过程中,我遇到了很多问题。多数问题源于自身失误,尤其是禁用警告提示所导致的隐患。当我最终启用警告后,便全力投入逐一修复所有问题的行动中。事实证明,这一过程非常困难。处理不可发送类型的问题是主要挑战之一。
我希望这些方法很有用。敬请联络,愿闻阁下高见!