【问题标题】:Combine: Going from Notification Center addObserver with selector to Notification publisher组合:从带有选择器的通知中心 addObserver 到通知发布者
【发布时间】:2019-10-25 13:53:25
【问题描述】:

我已经看到如何从一些 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")
    )

有人知道吗?谢谢!

【问题讨论】:

    标签: swift combine


    【解决方案1】:

    你说“我没有选择器”是对的,因为这就是其中的一半。您可以使用组合接收来自通知中心的通知

    另一半是您可以将处理通知的逻辑推送到合并管道中,以便正确的结果会在管道的末端弹出(如果它到达您)。

    老式的方式

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

    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 操作符将其安全地转换为卡片(如果它不是卡片,管道就会停止,好像什么都没发生)。我们只想要卡片的name,所以我们使用map 运算符来获取它。结果如下:

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

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

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

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

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

    如果您尝试一下,您会发现我们现在的情况是,每次用户点击一张卡片时,都会打印出卡片的名称。这就是我们之前的 register-an-observer-with-a-selector 方法的 Combine 等价物。

    【讨论】:

    • 当发布者在另一个 Viewcontroller 和订阅者在另一个 Viewcontroller 时如何使用它?就像我们对通知中心所做的那样?
    • 能否请您给我一些指导如何在 Comine 中处理这种情况 -> 我有一个包含内容视图的 VC(比如主 VC),其中包含另一个 vc 的时间,主 VC 如何当用户点击内容视图内的 vc 上的按钮时收到通知?
    • 这个讨论和我这个问答无关。请将您的问题作为一个真正的问题提出。
    【解决方案2】:

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

    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)
    

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

    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"
    

    【讨论】:

    • 谢谢,我会努力实现的!我也会尝试在这个 Q 中添加另一个赏金来给你一个赏金
    猜你喜欢
    • 2023-03-28
    • 1970-01-01
    • 2012-08-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-10
    • 2016-07-31
    • 2020-08-02
    相关资源
    最近更新 更多