NSNotification never being sent - swift

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.

Related

Selector function not being entered when observer is added

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.

NSWorkspace notification not received

My function isn't being executed when I switch apps:
public class ItemObserver: NSObject {
#objc public func recievedNotification(notification: NSNotification) {
print(notification.name)
print("s")
}
}
let observer = ItemObserver()
NSWorkspace.shared().notificationCenter.addObserver(observer, selector: #selector(ItemObserver.recievedNotification(notification:)), name: .NSWorkspaceDidActivateApplication, object: nil)
NSNotificationCenter doesn't retain the observer: it uses a zeroing weak reference, where possible. If you log its init and deinit, you'll see that it's being released right away.
Keep a strong reference to the observer.
So it turns out that NSNotifications don't run concurrently. I had a while loop further down.

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.

Call function on app termination in Swift

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.