合并:从带有选择器的通知中心 addObserver 到通知发布者

Combine: Going from Notification Center addObserver with selector to Notification publisher

我已经看到如何从一些 NotificationCenter 代码中使用 Publisher 过渡到 Combine,但还没有看到如何为类似的东西做到这一点:

        NotificationCenter.default.addObserver(
        self,
        selector: #selector(notCombine),
        name: NSNotification.Name(rawValue: "notCombine"),
        object: nil
    )

我已经看到它可以作为发布者使用,但是我没有selector并且我不确定该怎么做:

        NotificationCenter.default.publisher(
        for: Notification.Name(rawValue: "notCombine")
    )

有人知道吗?谢谢!

用例并不完全清楚,但这里有一个基础游乐场示例:

import Combine
import Foundation

class CombineNotificationSender {

    var message : String

    init(_ messageToSend: String) {
        message = messageToSend
    }

    static let combineNotification = Notification.Name("CombineNotification")
}

class CombineNotificationReceiver {
    var cancelSet: Set<AnyCancellable> = []

    init() {
        NotificationCenter.default.publisher(for: CombineNotificationSender.combineNotification)
            .compactMap{[=10=].object as? CombineNotificationSender}
            .map{[=10=].message}
            .sink() {
                [weak self] message in
                self?.handleNotification(message)
            }
            .store(in: &cancelSet)
    }

    func handleNotification(_ message: String) {
        print(message)
    }
}

let receiver = CombineNotificationReceiver()
let sender = CombineNotificationSender("Message from sender")

NotificationCenter.default.post(name: CombineNotificationSender.combineNotification, object: sender)
sender.message = "Another message from sender"
NotificationCenter.default.post(name: CombineNotificationSender.combineNotification, object: sender)

对于某些用例,您还可以将其设为仅组合解决方案而不使用通知

import Combine
import Foundation

class CombineMessageSender {
    @Published var message : String?
}

class CombineMessageReceiver {
    private var cancelSet: Set<AnyCancellable> = []

    init(_ publisher: AnyPublisher<String?, Never>) {
        publisher
            .compactMap{[=11=]}
            .sink() {
                self.handleNotification([=11=])
            }
            .store(in: &cancelSet)
    }

    func handleNotification(_ message: String) {
        print(message)
    }
}

let sender = CombineMessageSender()
let receiver = CombineMessageReceiver(sender.$message.eraseToAnyPublisher())
sender.message = "Message from sender"
sender.message = "Another message from sender"

你说 "I don't have a selector" 是对的,因为那是正确的一半。您可以使用 Combine 在没有选择器的情况下接收来自通知中心的通知。

要点的另一半是,您可以将处理通知的逻辑推送到 Combine 管道中,以便正确的结果在到达您时直接从管道的末端弹出。

老式的方式

假设我有一个卡片视图,当它通过发布通知被点击时会发出虚拟的尖叫声:

static let tapped = Notification.Name("tapped")
@objc func tapped() {
    NotificationCenter.default.post(name: Self.tapped, object: self)
}

现在让我们假设,为了示例的目的,游戏在收到这些通知之一时感兴趣的是发布的卡片的 name 属性 的字符串值通知。如果我们以老式方式执行此操作,那么获取该信息是一个两个阶段的过程。首先,我们必须注册才能接收通知:

NotificationCenter.default.addObserver(self, 
    selector: #selector(cardTapped), name: Card.tapped, object: nil)

然后,当我们收到通知时,我们必须查看它的 object 是否真的是一张卡片,如果是,则获取它的 name 属性 并执行一些东西:

@objc func cardTapped(_ n:Notification) {
    if let card = n.object as? Card {
        let name = card.name
        print(name) // or something
    }
}

合并方式

现在让我们使用 Combine 框架做同样的事情。我们通过调用其 publisher 方法从通知中心获取发布者。但我们不止于此。如果 object 不是卡片,我们不想收到通知,因此我们使用 compactMap 运算符将其安全地转换为卡片(如果不是卡片,管道就像什么都没发生一样停下来)。我们只想要 Card 的 name,所以我们使用 map 运算符来获取它。结果如下:

let cardTappedCardNamePublisher = 
    NotificationCenter.default.publisher(for: Card.tapped)
        .compactMap {[=13=].object as? Card}
        .map {[=13=].name}

假设 cardTappedCardNamePublisher 是我们视图控制器的一个实例 属性。那么我们现在拥有的是一个实例 属性,如果 Card 发布 tapped 通知,它会发布一个字符串,否则什么都不做。

当我说逻辑被推入管道时,你明白我的意思吗?

那么我们如何安排接收来自管道末端的内容呢?我们可以使用水槽:

let sink = self.cardTappedCardNamePublisher.sink {
    print([=14=]) // the string name of a card
}

如果您尝试一下,您会发现我们现在遇到的情况是,每次用户点击卡片时,都会打印卡片的名称。这就是我们之前使用选择器注册观察者方法的 Combine 等价物。