NotificationCenter not always calls observer when specified object is a String - swift

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.

Related

Very strange bug in this 1 line function

So I stumbled onto something really weird.
Here is a 1 line function
#objc func foo(value: Int = 1) {
print("value is \(value)")
}
I'm gonna set up a tap gesture in viewDidLoad to call that function:
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
self.view.addGestureRecognizer(tap)
Here's the handleTap method. Note how I call foo without any arguments.
#objc func handleTap() {
foo()
}
As you'd expect, it prints out a 1 every time.
Enter The Dragon 🐉
Here's where it gets weird. Let's change handleTap to only post a notification. And we'll add an observer for that notification in viewDidLoad to call foo, like so.
The notification:
extension Notification.Name {
static let didSomething = Notification.Name("didSometing")
}
The new handleTap method:
#objc func handleTap() {
NotificationCenter.default.post(name: .didSomething, object: nil)
}
And our addition to viewDidLoad:
NotificationCenter.default.addObserver(self, selector: #selector(foo), name: .didSomething, object: nil)
Now every time I tap, I get all kinds of values, here's what my output looks like:
value is 10748457248
value is 10748611248
value is 10748564928
value is 10748456000
value is 10748564256
value is 10748612688
value is 10748611824
value is 10748609616
value is 10748612496
value is 10748564592
Now if I change the notification observation to call a new method fooCaller (which is just a method that calls foo()), then I get all 1's.
So I figure it's something to do with obj-c, selectors and arguments, but thing is that my foo can be called without any arguments, like foo(), so I would reasonably expect #selector(foo) to have the same behaviour but it doesn't, and I don't know why.
In actuality, my function (which is represented here by foo) never took any arguments and was only called from notification observations. Then I later needed to call foo directly, passing something. So I gave it a default value so as not to disturb the current use of foo elsewhere, but it opened up the dark dimension instead.
There is sort of a bug, in the sense that, in my opinion, your code should not compile. And it does indeed, as you suspect, have to do with the tricky interface between Objective-C and Swift.
Let's look at the declaration of addObserver in Objective-C:
- (void)addObserver:(id)observer selector:(SEL)aSelector
name:(NSNotificationName)aName object:(id)anObject;
According to the docs, the selector must refer to a method that has
one and only one argument (an instance of NSNotification)
That's because the NSNotification can contain important information, such as its userInfo, that you might need to receive.
In other words, your foo, if it is to be the method called by the notification center to let you know of the notification, should have this signature:
#objc func foo(_ notification: Notification) {
And if it did, you'd be able to do stuff like this:
#objc func foo(_ notification: Notification) {
print(notification) // name = didSomething, object = nil, userInfo = nil
}
Great. But your foo does not have that signature; it types its parameter as Int instead. This is not identically what you have, but it might as well be:
#objc func foo(_ value: Int) {
Now, in my opinion, that should not be permitted. If you declare your foo that way, then when the time comes to use it as the selector...
NotificationCenter.default.addObserver(self, selector: #selector(foo), name: .didSomething, object: nil)
...the compiler should complain: "No, you can't do that, foo has the wrong type as its parameter!" But it doesn't.
So the Notification object arrives and is interpreted as an Int in accordance with its memory location — and that is what you are printing.
Your console log print these numbers because of this line:
NotificationCenter.default.addObserver(self, selector: #selector(foo), name: .didSomething, object: nil)
This function is passing the Notification as a parameter which causes these logs. Modify your function to receive the notification and handle it.
#objc func foo(notification: Notification) {
print("received notification: \(notification)")
}
Try passing the foo function as parameter for selector in tapGestureRecognizer, like this:
let tap = UITapGestureRecognizer(target: self, action: #selector(foo))
view.addGestureRecognizer(tap)
You'll see a similar result it's because because here the UITapGestureRecognizer is being passed to the foo function.

Use value of superclass in instance in Swift

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
}

Is removing a NotificationCenter observer that was created with closure syntax by name adequate?

I have a few notifications that were created using block / trailing closure syntax which look like this:
NotificationCenter.default.addObserver(forName: .NSManagedObjectContextObjectsDidChange, object: moc, queue: nil) { note in
// implementation
}
Which I was later removing by name, like this:
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.NSManagedObjectContextObjectsDidChange, object: moc)
My Question
Is this adequate? Or do I absolutely need to save the NSObjectProtocol to it's own property and remove that property with the following syntax?
NotificationCenter.default.removeObserver(didChangeNotification)
You absolutely need to store the return value in a property and remove that later on.
From https://developer.apple.com/reference/foundation/nsnotificationcenter/1411723-addobserverforname:
Return Value
An opaque object to act as the observer.
When you call any one of the removeObserver methods, the first parameter is the observer to remove. When you set up a block to respond to a notification, self is not the observer, NSNotificationCenter creates its own observer object behind the scenes and returns it to you.
Note: as of iOS 9, you are no longer required to call removeObserver from dealloc/deinit, as that will happen automatically when the observer goes away. So, if you're only targeting iOS 9, this may all just work, but if you're not retaining the returned observer at all, the notification could be removed before you expect it to be. Better safe than sorry.
To add to #Dave's answer, it looks like documentation isn't always 100% accurate. According to this article by Ole Begemann there is a contradiction in the doc and self-removing magic was not happening as of iOS 11.2 in his test app.
So that the answer is still "Yes, one needs to remove that observer manually" (and yes, self is not the observer, the result of addObserver() method is the observer).
Here an example with code, for how a correct implementation looks like:
Declare the variable that gets returned when you add the observer in your class A (the receiver of the notification or observer):
private var fetchTripsNotification: NSObjectProtocol?
In your init method add yourself as an observer:
init() {
fetchTripsNotification = NotificationCenter.default.addObserver(forName: .needsToFetchTrips, object: nil, queue: nil) { [weak self] _ in
guard let `self` = self else {
return
}
self.fetchTrips()
}
}
In the deinit method of your class, make sure to remove the observer:
deinit { NotificationCenter.default.removeObserver(fetchTripsNotification as Any) }
In your class B (the poster of the notification) trigger the notification like usually:
NotificationCenter.default.post(name: .needsToFetchTrips, object: nil)

Observer never called

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.

Doubts in NSNotification in Swift

I am developing my very first app for iOS and not understanding notifications.
I am sending notifications as:
DefaultCenter.postNotificationName("evRodadaAtualizei", object: nil)
In another class, I have a method that observes this notification:
DefaultCenter.addObserver(self, selector: Selector("Atualizar"),
name: "evRodadaAtualizei", object: nil)
My question is:
This observer will listen any notification with that name? It is not important the class where notification was declared? In other words, is possible to have a place to put all the notifications (like a notification library), because all of them are independent of the class?
If I am understanding correctly, this is very different of the concept of events in C# or VB.Net where events belongs to classes.
Notifications in Cocoa work inter-class. It doesn't matter where the notification is created or observed.
However, note the object parameter on the postNotificationName method. If set, this should correspond to the object posting the notification. If you only want to observe notifications for a given object, set the object parameter to that object when you add the observer. e.g.
class MyObjectClass {
func doSomething() {
// Do something and then notify
DefaultCenter.postNotificationName("evRodadaAtualizei", object: self)
}
}
class MyObserverClass {
func startProcess() {
var myObject = MyObjectClass()
DefaultCenter.addObserver(self, selector: Selector("Atualizar"), name: "evRodadaAtualizei", object: myObject)
}
func Atualizar() {
}
}