Swift 6 迁移:处理可发送上下文中的弱委托

问题描述 投票:0回答:1

我正在尝试了解 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

可能的解决方案

在我看来,我有几个选择,但每个选择都有自己的权衡:

  1. 将 ServiceProvidable 隔离给参与者(例如,@MainActor):这种方法将涉及对其他代码进行更大的重构

  2. 围绕委托创建一个线程安全的包装器:我可以实现一个包装器,以确保对委托的线程安全访问 然后将包装器标记为@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 中的弱委托?

swift multithreading concurrency thread-safety swift6
1个回答
0
投票
如果编译器可以确定

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
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.