I am trying to write a chat application and I am using SignalR for this. I recently started to refactor the code to use a handler for the SignalR/SwiftR functions. I am creating an instance, when I call the SignalR handler from the chat viewController. Now I have a function, which is triggered inside the handler instance and from there I am trying to execute a function in the viewController. I tried to do this with an instance. But now I don't have any data in my arrayMessage because I am creating a new instance of the class. Is there a way to get the array of the normal class?
If not, what's the best way to execute this function?
This should execute the recieveMessage:
chatHub.on("CommunityMessage") { args in
if let m: AnyObject = args![0] as AnyObject!{
SignalRViewController.instance.recieveMessage(m: m)
}
}
recieveMessage function, where I don't have data in the arrayMessage:
func recieveMessage(m : AnyObject){
let message = m.object(forKey: "Message") as! String
let index = (self.arrayMessage.count - 1)
print(self.arrayMessage)
}
In your chathub-callback you can post a notification with the message attached
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "ChatHubMessageReceived"), object: m)
In the ViewController you can then subscribe yourself to the message
NotificationCenter.default.addObserver(self,
selector: #selector(receiveMessage),
name: NSNotification.Name(rawValue: "ChatHubMessageReceived"),
object: nil)
You will have to adjust the method signature to one that takes a notification
func recieveMessage(notification: Notification) {
let message = notification.object
}
Related
In my first view controller, I post the notification with the following code:
NotificationCenter.default.post(name: Notification.Name("date"), object: formattedDate)
I then "receive" the notification in a second view controller with the following code:
func receiveNotification () {
NotificationCenter.default.addObserver(self, selector: #selector(self.didGetTheDate(_:)), name: NSNotification.Name("date"), object: nil)
}
#objc
func didGetTheDate(_ notification: Notification) {
print("In did get date")
date = notification.object as! String
}
However, the function "didGetTheDate" never gets called.
I have triple checked that the function "receiveNotification" gets called as I have added print statements to check this.
Can somebody please help me with this.
NSNotificacionCenter is a variation of the Observer Pattern, you can read about it here
This means that you will have to register an Observer before posting any notification. If you post anything before that, the NSNotificationCenter will look at the observer for name and will see that nobody is listening for it, thus nothing will happen.
I have been trying to check why my observer is sometimes called and sometimes isn't when specifying a String object to filter out notifications.
My original code:
NotificationCenter.default.addObserver(self, selector: #selector(update), name: .pathContentsChanged, object: self.folder.path) // Where path is something like "/user/folder"
#objc func update()
{
// Not always called
}
My current workaround, which confirms that the notification object does match self.folder.path:
NotificationCenter.default.addObserver(self, selector: #selector(pathContentsChanged(_:)), name: .pathContentsChanged, object: nil) // No object specified anymore
#objc func pathContentsChanged(_ notification: Notification)
{
// This does get called every time
guard let path = notification.object as? String,
path == self.folder.path else
{
return
}
self.update() // notification.object and self.folder.path do match
}
Is it inherent at how Strings work on Swift? Is it because notification.object could be a bridged NSString?
NotificationCenter filters notification per-object comparing objects by-reference, and not by isEqual as you'd might expect. So posting notification with different strings (even equal by content) might not result your observer to be filtered out.
In general, String is not that object to be used in notification.object (due to its nature, constants, bridged, etc.)... Most usual scenario is to have some your custom object (that owns workflow) to be the originator of notification (so be notification object).
And if you want to have strings in object then yes, your observers have to be non filtered 'object: nil' and act conditionally inside handler.
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.
The old-fashioned way
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
}
}
The Combine way
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"
What am I doing wrong with the #selector assignment when I add the notification observer?
NotificationCenter.default.addObserver(self, selector: #selector(reloadData), name: NSNotification.Name(rawValue: "reloadCollectionData"), object: nil)
func reloadData(completionHandler: ((Bool) -> Void)? = nil ) {
mainCollectionView.reloadData()
completionHandler?(true)
}
The app crashes whenever I post the notification:
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadCollectionData"), object: nil)
I have already tried things like #selector(reloadData(completionHandler: nil)
EDIT:
I have tried the selector #selector(reloadData(completionHandler:)) but it still crashes n the line where I post a notification, with the error message:
Thread 1: EXC_BAD_ACCESS (code=1, address=0x8)
When I change the code as follows everything works fine but it is not really nice to create a function just to call another function
NotificationCenter.default.addObserver(self, selector: #selector(testCall), name: NSNotification.Name(rawValue: "reloadCollectionData"), object: nil)
func testCall() {
self.reloadData()
}
A completion handler in a notification selector method is nonsensical (and illegal). What do you expect is the object which is supposed to be the receiver of the completion handler ... ?
The syntax of a notification selector is very similar to a target/action selector. The passed parameter must be the type of the affected object (here Notification).
func reloadData(_ notification : Notification) { ...
The alternative is the block based API of Notification which is able to capture values of the enclosing function / method.
The only way to pass custom data in a notification object with specified selector is the (optional) userInfo dictionary.
I have two functions
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
NSNotificationCenter.defaultCenter().addObserverForName("personalDataDidLoad", object: self, queue: NSOperationQueue.mainQueue()) {_ in
print("Received notification")
self.showPersonalData()
}
loadPersonalData()
}
func loadPersonalData() {
//load data
print("personal data loaded")
NSNotificationCenter.defaultCenter().postNotificationName("personalDataDidLoad", object: nil)
but for some reason this outputs
personal data loaded
instead of the expected
personal data loaded
Received notification
I'm probably missing something obvious, but I don't see it right now....
I also tried addObserver with selector: "showPersonalData:" but this throws an unrecognized selector exception..
The problem is with the 2nd parameter in postNotificationName and addObserverForName: object. When you add an observer, and pass a non-nil object value, this means that the observer block will run when a notification comes from that object, and from that object only.
However, when you fire the notification, you do object: nil. So your notification is ignored.
On the other hand, passing a nil value for object means, "I want to receive this notification regardless of who sends it".
So you need to make sure the object value is the same in both places: either self or nil.
Is there a reason you need to use addObserverForName(_:object:queue:usingBlock:)?
Try this instead:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
NSNotificationCenter.defaultCenter().addObserver(self, "personalDataDidLoadNotification:", name: "personalDataDidLoad" object: nil)
loadPersonalData()
}
func loadPersonalData() {
//load data
print("personal data loaded")
NSNotificationCenter.defaultCenter().postNotificationName("personalDataDidLoad", object: nil)
}
func personalDataDidLoadNotification(notification: NSNotification) {
print("notification recieved")
}
Another answer to the title question (but not this example) but will hopefully help others in the situation I have been in for the last 3 hours:
Make sure your notificationcenter observer is added inside a class that has a persisted instance. I created the observer inside a class that was called as e.g. MyClass().setupNotification inside a local method of another class.
This meant the observer was immediately deleted and didnt persist against any instance.
Schoolboy error - but hope this helps others searching for this.