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() {
}
}
Related
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.
NotificationCenter.default.addObserver(forName: NSNotification.Name.NSManagedObjectContextObjectsDidChange,
object: context, queue: nil) { notification in /.../ }
When you register for NSNotifcation like this, according to the documentation you don't need to unregister
Discussion If your app targets iOS 9.0 and later or macOS 10.11 and
later, you don't need to unregister an observer in its dealloc method.
Otherwise, you should call this method or removeObserver: before
observer or any object specified in
addObserverForName:object:queue:usingBlock: or
addObserver:selector:name:object: is deallocated.
But before iOS 9.0 you would have to unregister like this
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self,
name: NSNotification.Name.NSManagedObjectContextObjectsDidChange,
object: nil)
}
But how does NSNotificationCenter know to unregister my object when it gets deallocated?
I'm trying to implement a similar kind of logic and I would also like this auto unregister feature. Consider:
Class Publisher {
var callback:()-> Void
func subscribe( callback:()-> Void) {
self.callback = callback
}
}
Class Subscriber {
var publisher = Publisher()
init() {
publisher.subscribe { /*do something*/ }
}
deinit {
publisher.unsubscribe() //I want this to happen automatically
}
}
But how does NSNotificationCenter know to unregister my object when it gets deallocated?
The notification center's reference to the target is ARC-weak. So it is automatically nilified when the target goes out of existence. So when the notification center gets ready to post to that target, it looks first to see if it is nil, and if it is, it says "Oh, well, that target is gone," and unregisters the target.
As for your main question, that is exactly how publish-and-subscribe works already with the Combine framework. The subscriber is wrapped in an AnyCancellable and stored, and when it is released it is cancelled and sends a cancel message up the pipeline to the publisher.
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)
How can I call a function that is within an SKScene class when my app is terminated by the user?
I need to modify a value and save it to NSUserDefauts when the app is terminated.
You can register to receive a notification when your app is about to terminate. To do this, add an observer to the default notification center by
Swift 5:
NotificationCenter.default.addObserver(self, selector: #selector(saveData), name: UIApplication.willTerminateNotification, object: nil)
Swift 3/4:
// Add this to didMoveToView in your SKScene subclass
NotificationCenter.default.addObserver(self, selector: #selector(saveData), name: NSNotification.Name.UIApplicationWillTerminate, object: nil)
Add the following method to your SKScene subclass. The method will be called before the app terminates. It must be "exposed" to Objective-C by adding #objc so the notifier can use #selector().
#objc func saveData(notification:NSNotification) {
// Save your data here
print("Saving data...")
}
In Swift 3 and 4 you have something like that:
in your viewDidLoad
NotificationCenter.default.addObserver(self, selector: #selector(toDoSomething), name: NSNotification.Name.UIApplicationWillTerminate, object: nil)
and than you have that method to be called
func suspending () {
print("toDoSomething...")
}
There are a few methods in UIAppDelegate that will help you. Take a look at applicationWillTerminate(_:) and applicationWillResignActive(_:). From there you see what state your app is in and do perform the appropriate actions.
I'm trying to use the NSNotificationCenter with Swift and I'm running into a problem. I'm trying the following:
class MyClass {
init() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "stopTimer", name: UIApplicationDidEnterBackgroundNotification, object: [])
}
func stopTimer() {
println("Entered background!")
}
}
When hitting the home button, the observer is never calling the selector, therefore my message is never being printed out.
Notice that I send an empty object at the end, the method signature for addObserver is expecting an implicitly unwrapped object and setting it to nil results in an EXC_BAD_ACCESS error when the app enters background.
Any ideas?
EDIT
I forgot to mention that this code is being executed from a framework included in my project.
Executing the code gives me a bad access abort signal.
You should be able to pass nil for an optional argument. Can you elaborate more on what happens if you do? This is working for me:
func init() {
super.init()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "stopTimer", name: UIApplicationDidEnterBackgroundNotification, object: nil)
}
func stopTimer() {
println("Entered background!")
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
Alright, I figured this out. 2 problems:
There was no strong reference to the instance of my class in my app
My class wasn't extending NSObject
Now, I can set object: nil and everything works fine.
Thanks for all your help! :)
Perhaps you should try implementing applicationDidEnterBackground(application: UIApplication) instead?
To further troubleshoot, you may also want to add a log statement to your app delegate's deinit to see if the delegate gets deallocated after entering the background.