How can I ensure, that an observer within a NotificationCenter is called only once - especially when the app crashes/has to be stopped by Xcode?
Let's assume I want to print the notification NSNotification.WhatHaveYou.
init() {
NotificationCenter.default.addObserver(forName: NSNotification.Name.WhatHaveYou, object: nil, queue: OperationQueue.main, using: { (notification) in
print(notification)
})
}
Which works fine.
Apple stated in its documentation for NotificationCenter.default.addObserver(forName:, object:, queue:, using:):
The block to be executed when the notification is received.
The block is copied by the notification center and (the copy) held
until the observer registration is removed.
But how can I make this observer registration is removed, so that for the next app start there is no further notification registered?
Especially when the app crashes or I stop the application via Xcode, the observer is not removed, so the notification will show up multiple times - to be more precise: lastNumberOfOccurences = lastNumberOfOccurences +1
How to handle that properly?
Update
So far I find these resources
http://benscheirman.com/2012/01/careful-with-block-based-notification-handlers/
http://sealedabstract.com/code/nsnotificationcenter-with-blocks-considered-harmful/
Observers that are subscribed to NotificationCenter cannot survive app restarts. This is because the objects that you've added get removed from memory as soon as the app dies, be it a crash or a normal termination. This said, all observers need to be added during runtime of your app and they can be also removed during your app's runtime. As soon as the app terminates, all associated memory gets freed, including subscribed observers.
Related
I am using NSNotificationCenter to set a variable shared when a notification comes in across 3 TableViewControllers (all subclassed from the same class).
All works fine except for the following scenario:
The user is in the main view (the view that actually processes/responds to notifications) when the app goes to the background (e.g. user presses Home button). If a notification comes in and the user launches the app either from the icon or from the alert the app behaves as if it needs to process not only the last notification but also all the notification up-to-now.
For example: let;'s assume that the app processed 5 notification already. The app moved to the background and then a notification came in. The user launches the app from the icon which causes the app to move to the foreground. The app will trigger 1 action per notification.
This is not the case if the user was in any other screen before the app moved to background. However, if the user moved to the main view, the notification was processed and the app moved to background again, on the next notification, the app will process 7 notifications (the 5 we had + 1 previous + 1 current).
While conceptually it seems as if my app is spawning n-observers, it is not clear to me why. I set an observer in the base class in viewWillAppear and remove in viewWillDisappear (also tried a deinit block - no change in behaviour).
Any idea why the observer accumulate all notifications? Is this the normal behaviour for an observer? If so, how do I remove 'observed and processed' messages?
The solution to my problem was to remove the observer before adding it.
The problem itself came down to the fact that the order of cleanup (i.e. add observer in viewWillAppear, remove observer viewWillDisappear) did not match my expectations. The result was that the observer was being added again and again but the corresponding 'remove' did not actually 'happen'.
I guess it was due to the fact that adding/removing observers took place in the base class (and not sub-class).
I wonder at what point one would call activateSession() on a WCSession object on the watch and on the iOS device.
In the documentation it says:
Always assign a delegate and activate your session before calling any session-related methods. The session must be configured and activated before sending messages or obtaining information about the state of the connection.
At first thought I put my code to initialise the session:
if (WCSession.isSupported()) {
session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
in viewDidLoad on the iOS device and in willActivate on the watch side.
It works... but I don't think it's a good solution.
I'm not too familiar with the app-lifecycle yet but as far as I understand those are called every time the app's are opened.
Does that result in a "reconnect" every time one of the apps is opened?
Where would be a good place to place that code?
When you put the WCSession code in viewDidLoad and willActivate it is not only called when the app is opened but every time the view controller that contains the code is shown. So that is not an ideal place.
The best place to put this code is in application:didFinishLaunchingWithOptions in your app's AppDelegate and in applicationDidFinishLaunching in your watch extensions's ExtensionDelegate
You can put all the session handling into a singleton class, as suggested in this great tutorial by #NatashaTheRobot.
That way the session is only created once for the time the app in being held in memory.
EDIT
As ccjensen pointed out in his comment, if you are using the connection for a Complication, Notification or Glance update you have to activate the session in the ExtensionDelegate's init method. applicationDidFinishLaunching will not be called in those cases.
I have a server that successfully generates push notifications and sends them to devices. On the app side, if the user is already in the app or if the user enters the app from the notification I can easily 'find' the notification and run the necessary code to action the notification (app delegate gets invoked and the notification is stored in NSUserDefaults which I can then access throughout the app).
However, if the user has the app running in the background (for example, user was in the app, but then switched to another app), when the user reopens the app from the app icon, the app simply returns to the last state it was in and no code actually gets invoked (which is kind of what you expect).
What I am trying to figure out is how to either invoke AppDelegate again so that I can extract the notification before I return to the current state or how do I invoke some code so that I can intercept the notification.
Thoughts?
If I understood your question correctly, you want to be notified when coming out of background state. How the appdelegate method
func applicationDidBecomeActive(application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
Here is my solution.
On 'App Resume', In AppDelegate, I check the badge and store push if badge value is greater than 0:
func applicationDidBecomeActive(application: UIApplication) {
let installation = PFInstallation.currentInstallation()
if installation.badge != 0 {
NSUserDefaults.standardUserDefaults().setObject(true, forKey: "push")
NSNotificationCenter.defaultCenter().postNotificationName("indicatePush", object: nil)
installation.badge = 0
installation.saveEventually()
}}
The call to NSNotificationCenter, invokes a registered method in my base class, where I set the push variable to true
In the sub-class, on viewDidLoad I register two methods (you might only need one of them):
NSNotificationCenter.defaultCenter().addObserver(self, selector: "appplicationIsActive:", name: UIApplicationDidBecomeActiveNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "applicationEnteredForeground:", name: UIApplicationWillEnterForegroundNotification, object: nil)
Add the call to the 'pull from server' to one of the two methods. In my case:
func appplicationIsActive (notification: NSNotification){
refresh()
}
You're done!
I've read a lot of suggestions for the right place to call .removeObserver for NSNotificationCenter since viewDidUnload is not an option.
I was just wondering if the new deinit() in Swift would be a good choice?
-nick
It really depends on the role of the class where you subscribe to NSNotificationCenter notifications. If you are subscribing in:
UIView
Then you should unsubscribe as soon as view gets invisible to the user. To save CPU cycles and not consume resources while user does not see the view.
UIViewController
Here it also depends on kind of action that you are going to perform in response to notification. If it is just a UI adjustment that you should unsubscribe as soon as view controller disappears from the screen.
You App Service layer
Here it is OK to have .removeObserver inside deinit(). however even here I tend to suggest you to be more explicit about when you subscribe and unsubscribe from NSNotificationCenternotifications and put them in start and stop methods of your service.
If you were previously calling removeObserver in viewDidUnload/dealloc/deinit, then starting with iOS 9.0 and macOS 10.11, you don't need to call it anymore:
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.
source: https://developer.apple.com/documentation/foundation/notificationcenter/1413994-removeobserver
I have a UIViewController which has objects perform a series of tasks on background threads as it appears, and it's registered as an observer to them. When they get called for the first time, it stops being an observer.
I realised I could save user time if these tasks were performed before the view controller is shown, so I initialized it and called a method which ran these tasks.
Then I started getting errors along the lines of:
An instance [insance] of class [class name] was deallocated while key value observers were still registered with it.
How can I prevent this from happening? If i show the view controller straight away, this works no problem.
I'd recommend, that you add a call [notificationCenter removeObserver: self] in method dealloc of those classes, which you intend to use as observers, as it is the last chance to unregister an observer cleanly.