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

SRM*_*RMR 5 swift combine

我已经从一些NotificationCenter代码中看到了如何使用 Publisher 转换到 Combine ,但还没有看到如何为以下内容执行此操作:

        NotificationCenter.default.addObserver(
        self,
        selector: #selector(notCombine),
        name: NSNotification.Name(rawValue: "notCombine"),
        object: nil
    )
Run Code Online (Sandbox Code Playgroud)

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

        NotificationCenter.default.publisher(
        for: Notification.Name(rawValue: "notCombine")
    )
Run Code Online (Sandbox Code Playgroud)

有人知道吗?谢谢!

mat*_*att 21

你说“我没有选择器”是对的,因为那是一半。您可以使用Combine在没有选择器的情况下从通知中心接收通知。

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

老式的方式

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

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

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

NotificationCenter.default.addObserver(self, 
    selector: #selector(cardTapped), name: Card.tapped, object: nil)
Run Code Online (Sandbox Code Playgroud)

然后,当我们收到通知时,我们必须查看它是否object真的是 Card,如果是,则获取它的name属性并对其进行处理:

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

组合方式

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

let cardTappedCardNamePublisher = 
    NotificationCenter.default.publisher(for: Card.tapped)
        .compactMap {$0.object as? Card}
        .map {$0.name}
Run Code Online (Sandbox Code Playgroud)

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

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

那么我们将如何安排接收从管道末端出来的东西?我们可以使用水槽:

let sink = self.cardTappedCardNamePublisher.sink {
    print($0) // the string name of a card
}
Run Code Online (Sandbox Code Playgroud)

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


ber*_*rni 10

用例并不完全清楚,但这里有一个基本的操场示例:

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{$0.object as? CombineNotificationSender}
            .map{$0.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)
Run Code Online (Sandbox Code Playgroud)

对于某些用例,您还可以在不使用通知的情况下使其成为仅组合的解决方案

import Combine
import Foundation

class CombineMessageSender {
    @Published var message : String?
}

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

    init(_ publisher: AnyPublisher<String?, Never>) {
        publisher
            .compactMap{$0}
            .sink() {
                self.handleNotification($0)
            }
            .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"
Run Code Online (Sandbox Code Playgroud)