【问题标题】:In Swift Combine, is the "root" object always a Subject?在 Swift Combine 中,“根”对象总是一个 Subject 吗?
【发布时间】:2023-09-11 23:16:01
【问题描述】:

Swift Combine 上的Apple WWDC 视频中,他们总是使用NSNotificationCenter 作为消息的发布者。但是,Publisher 似乎没有任何实际按需发送消息的能力。该功能似乎位于Subject

我是否正确假设 Subject 因此必须是 Publishers 的任何链的根对象? Apple 提供了两个内置主题:CurrentValueSubjectPassthroughSubject

但我假设我可以使用适当的协议编写自己的Subject

【问题讨论】:

    标签: swift swift5 combine


    【解决方案1】:

    在 Swift Combine 中,Publishers 是一种协议,用于描述可以随时间传输值的对象。

    Subject 是一个扩展的发布者,它知道如何强制发送。

    Publisher 和 Subject 都不是具有实现的具体类;它们都是协议。

    看看 Publisher 协议(记住 Subject 是一个扩展的 Publisher):

    public protocol Publisher {
    
        associatedtype Output
    
        associatedtype Failure : Error
    
        func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input
    }
    

    要构建自定义发布者,您只需要实现接收功能(并提供类型信息),您可以在其中访问订阅者。您将如何从发布者内部向该订阅者发送数据?

    为此,我们查看订阅者协议以了解可用的内容:

    public protocol Subscriber : CustomCombineIdentifierConvertible {
    ...
    
        /// Tells the subscriber that the publisher has produced an element.
        ///
        /// - Parameter input: The published element.
        /// - Returns: A `Demand` instance indicating how many more elements the subcriber expects to receive.
        func receive(_ input: Self.Input) -> Subscribers.Demand
    }
    

    只要您保存了对已连接的任何/所有订阅者的引用,您的发布者就可以通过在订阅者上调用 receive 轻松地将更改发送到管道中。但是,您必须自行管理订阅者和差异更改。

    Subject 的行为相同,但不是将更改流式传输到管道中,它只是提供一个 send 函数供其他人调用。 Swift 提供的两个具体的 Subjects 具有额外的特性,比如存储。

    TL;DR 更改不会发送给发布者,而是发送给订阅者。主题是可以接受某些输入的发布者。

    【讨论】:

    • 对,但我肯定遗漏了一些明显的东西,因为我无法看到没有Subject 的消息实际上是如何发送给发布者的。我可以看到它在链中后是如何传递的,但只有Subject 协议上有send() 方法。如果我有一个要广播更改的自定义类,那么我公开给其他人订阅的Publisher 必须实际上是Subject,否则我的自定义类实际上如何向订阅者发送消息?
    • 非常感谢。我仍然感到困惑的部分是出版商的所有链接如何发挥作用。我有一个自定义类,它是发布者。它在一个数组中跟踪它接收到的每个订阅者。当它想要发送消息时,它会迭代数组并在每个Subscriber 上调用receive()。这基本上是具有多个委托的旧 Obj-C 委托模式。我的自定义发布者如何暴露自己,以便我可以与 debounce() 之类的东西链接,或者 Subscriber 可以创建自己的 Publisher 链。
    • 组合是使用操作符完成的,它接受一个输入发布者和一个输出订阅者。 Apple 已扩展 Publisher 协议以添加通用运算符(链接)的功能,但如果您正在构建自己的运算符,则需要手动订阅发布者并添加下游订阅者。例如。只需初始化一个 debounce Operator 并将其连接起来(或在 Publisher 上使用 .debounce())。
    • 我会的。运营商之间的联系方式对我来说仍然没有意义。如果我的自定义类是Publisher 并且引用了它的所有Subscribers,那么如上所述,它只在每个Subscriber 上调用receive。如果我的班级创建了一个debounce 运算符并将每个Subscriber 连接到它,那么我的班级如何通过debounce 运算符“发送”一条消息,然后将它发送给每个Subscriber?如果我在自定义类中使用 Subject 作为根对象,这是有道理的,但当我的类本身是 Publisher 时则不然。
    • Debounce 是您的发布者有权访问的订阅者,因此无论何时您拨打receive,您实际上是在向 debounce 发送数据,而不是您的订阅者。换句话说,运营商既是订阅者(对于您的发布者)又是发布者(对于您的订阅者,或者对于链中的下一个运营商)。需要查看代码以进一步提供帮助,但通常操作员在发布者之外(除非您只是包装现有发布者)。