我正在尝试了解 Swift 6 的并发模型,同时迁移一些使用弱委托的代码,并且我希望最大限度地减少更改。假设我有一个像这样定义的委托:
protocol ServiceDelegate: AnyObject {
func doSomething()
}
还有一个服务接口:
protocol ServiceProvidable {
var delegate: ServiceDelegate? { get set }
func load()
}
实现看起来像这样:
final class ServiceProvider: NSObject, ServiceProvidable, URLSessionDelegate {
weak var delegate: ServiceDelegate?
func load() {
delegate?.doSomething()
}
}
启用 Swift 6 后,我收到以下错误:
Stored property 'delegate' of 'Sendable'-conforming class 'ServiceProvider' is mutable
可能的解决方案
在我看来,我有几个选择,但每个选择都有自己的权衡:
将 ServiceProvidable 隔离给参与者(例如,@MainActor):这种方法将涉及对其他代码进行更大的重构
围绕委托创建一个线程安全的包装器:我可以实现一个包装器,以确保对委托的线程安全访问 然后将包装器标记为@unchecked Sendable。这就是这个 可能看起来像:
protocol ServiceDelegate: AnyObject {
func doSomething()
}
protocol SafeServiceDelegateProvidable: Sendable {
var delegate: ServiceDelegate? { get set }
}
protocol ServiceProvidable {
var safeDelegate: SafeServiceDelegateProvidable { get }
func load()
}
final class ServiceProvider: NSObject, ServiceProvidable, URLSessionDelegate {
let safeDelegate: SafeServiceDelegateProvidable = SafeServiceDelegateProvider()
func load() {
safeDelegate.delegate?.doSomething()
}
}
final class SafeServiceDelegateProvider: SafeServiceDelegateProvidable, @unchecked Sendable {
private var queue = DispatchQueue(label: "queue")
private weak var _delegate: ServiceDelegate?
var delegate: ServiceDelegate? {
get {
queue.sync { _delegate }
}
set {
queue.sync { _delegate = newValue }
}
}
}
但是,有一个复杂的情况:
safeDelegate
在初始化后需要更新(例如service.safeDelegate.delegate = self
),并且由于它不能是let常量,所以将其更改为var会导致错误: Stored property 'safeDelegate' of 'Sendable'-conforming class 'ServiceProvider' is mutable
问题
解决这个问题最简单或最有效的方法是什么?鉴于并发性的变化,是否有更好的方法来处理 Swift 6 中的弱委托?
safeDelegate.delegate
具有引用语义,则可以设置
safeDelegate
。您可以将协议限制为 AnyObject
:
protocol SafeServiceDelegateProvidable: AnyObject, Sendable { .. }
或者,您可以将
safeDelegate
的类型更改为具体类 SafeServiceDelegateProvider
。我不明白 SafeServiceDelegateProvidable
在这里有什么用处。
protocol ServiceProvidable {
var safeDelegate: SafeServiceDelegateProvider { get }
func load()
}
final class ServiceProvider: NSObject, ServiceProvidable, URLSessionDelegate {
let safeDelegate = SafeServiceDelegateProvider()
func load() {
safeDelegate.delegate?.doSomething()
}
}
正如 Rob 在评论中所说,要实际上使
SafeServiceDelegateProvider
满足Sendable
的要求,ServiceDelegate
也需要是Sendable
。
protocol ServiceDelegate: AnyObject, Sendable {
func doSomething()
}
否则,
SafeServiceDelegateProvider
跨线程发送仍然不安全。考虑:
class NotSendable: ServiceDelegate {
var someProperty = 0
func doSomething() {
someProperty += 1
}
}
@MainActor
class Foo {
let notSendable = NotSendable()
func doSomething() {
let provider = SafeServiceDelegateProvider()
provider.delegate = notSendable
Task.detached {
// here I sent 'provider' to some other thread
// doSomething is going to increment someProperty
// this is going to race with the line 'notSendable.someProperty += 1' just below
provider.delegate?.doSomething()
}
notSendable.someProperty += 1
}
}