I've seen how to transition to Combine using a Publisher from some NotificationCenter
code, but have not seen how to do it for something like:
NotificationCenter.default.addObserver(
self,
selector: #selector(notCombine),
name: NSNotification.Name(rawValue: "notCombine"),
object: nil
)
I've seen that this is available as a Publisher, but I don't have a selector
and am not sure what to do for it:
NotificationCenter.default.publisher(
for: Notification.Name(rawValue: "notCombine")
)
Does anyone know? Thanks!
You're right to say "I don't have a selector", as that is half the point right there. You can receive notifications from the notification center without a selector using Combine.
The other half of the point is that you can push your logic for dealing with the notification up into the Combine pipeline, so that the correct result just pops out the end of the pipeline if it reaches you at all.
Let's say I have a Card view that emits a virtual shriek when it is tapped by posting a notification:
static let tapped = Notification.Name("tapped")
@objc func tapped() {
NotificationCenter.default.post(name: Self.tapped, object: self)
}
Now let's say, for purposes of the example, that what the game is interested in when it receives one of these notifications is the string value of the name
property of the Card that posted the notification. If we do this the old-fashioned way, then getting that information is a two-stage process. First, we have to register to receive notifications at all:
NotificationCenter.default.addObserver(self,
selector: #selector(cardTapped), name: Card.tapped, object: nil)
Then, when we receive a notification, we have to look to see that its object
really is a Card, and if it is, fetch its name
property and do something with it:
@objc func cardTapped(_ n:Notification) {
if let card = n.object as? Card {
let name = card.name
print(name) // or something
}
}
Now let's do the same thing using the Combine framework. We obtain a publisher from the notification center by calling its publisher
method. But we don't stop there. We don't want to receive a notification if the object
isn't a Card, so we use the compactMap
operator to cast it safely to Card (and if it isn't a Card, the pipeline just stops as if nothing had happened). We only want the Card's name
, so we use the map
operator to get it. Here's the result:
let cardTappedCardNamePublisher =
NotificationCenter.default.publisher(for: Card.tapped)
.compactMap {$0.object as? Card}
.map {$0.name}
Let's say that cardTappedCardNamePublisher
is an instance property of our view controller. Then what we now have is an instance property that publishes a string if a Card posts the tapped
notification, and otherwise does nothing.
Do you see what I mean when I say that the logic is pushed up into the pipeline?
So how would we arrange to receive what comes out of the end of the pipeline? We could use a sink:
let sink = self.cardTappedCardNamePublisher.sink {
print($0) // the string name of a card
}
If you try it, you'll see that we now have a situation where every time the user taps a card, the name of the card is printed. That is the Combine equivalent of our earlier register-an-observer-with-a-selector approach.
The use case is not entirely clear, but here a basics playground example:
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)
For some use cases you can also make it a combine only solution without using notifications
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"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With