Cocoa: saveAction called 2 times with notification - objective-c

I have a custom class for NSButton called MyButton where I post a notification for a quicksave
MyButton.m:
-(void)mouseDown:(id)sender{
[super mouseDown:sender];
[super mouseUp:sender];
[[NSNotificationCenter defaultCenter] postNotificationName:#"quickSave" object:nil userInfo:nil];
}
in AppDelegate I get the notification for the quick save:
AppDelegate.m:
- (IBAction)saveAction:(id)sender{
NSLog(#"Saving...");
NSError *error = nil;
if (![[self managedObjectContext] commitEditing]) {
NSLog(#"%#:%# unable to commit editing before saving", [self class], NSStringFromSelector(_cmd));
}
if (![[self managedObjectContext] save:&error]) {
[[NSApplication sharedApplication] presentError:error];
}
}
-(void)awakeFromNib{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(saveAction:) name:#"quickSave" object:nil];
}
Via the NSLog "Saving..." I see that the saveAction is called 2 times. Why?
P.S: the notification calls 2 times every function I insert in the selector: field, so maybe it has to do with the -(void)awakeFromNib{...} because I see that it's called twice (there are two different self inside the awakeFromNib).
UPDATE: I've "solved" the problem binding in Interface Builder the Application as an AppDelegate delegate and then adding the [[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(saveAction:) name:#"quickSave" object:nil]; inside the -(void)applicationDidFinishLaunching:(NSNotification *)aNotification{...}. I don't know if it's a real solution and obviously it is not an answer to my question (why awakeFromNib is called 2 times), but it might be useful to someone. Does anyone have a clue?
UPDATE2: the right managedobjectcontext is the one called in awakeFromNib the second time, the first one (identical in awakeFromNib and applicationDidFinishLaunching) is wrong.
My app is a statusbar app, the first awakeFromNib is called when i start the app and the second one when a preference window is opened.

The log message indicates that two different instances of AppDelegate receive a single notification. Probably you instantiated AppDelegate twice. Ensure you are not manually doing [[AppDelegate alloc] init] or something, and not putting more than one AppDelegate object in NIB.

Related

nsnotification approach for session inactivity in objective c

In sesssion inactivity implementation for my project. I have created a NSNotification in RootViewController class of project.
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle: #"Close"
style: UIBarButtonItemStyleDone
target: self
action: #selector(closeModal)];
UIImage *image = [UIImage imageNamed:#"fidelity_logotype"];
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, image.size.width, image.size.height)];
[imageView setImage:image];
[self.navigationItem setTitleView:imageView];
self.navigationController.view.backgroundColor = [UIColor fidelityGreen];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationDidTimeout:) name:#"ApplicationTimeout" object:nil];
}
- (void) applicationDidTimeout:(NSNotification *) notif
{
NSLog(#"I m here");
BCDSessionInactivityViewController *sessionView=[[UIStoryboard storyboardWithName:#"Main" bundle:nil] instantiateViewControllerWithIdentifier:#"InactivityViewController"];
sessionView.modalPresentationStyle = UIModalPresentationFormSheet;
sessionView.preferredContentSize = CGSizeMake(838,340);
[[self topViewController] presentViewController:sessionView animated:YES completion:nil];
}
and in logoutviewcontroller, i am removing this observer written below
- (IBAction)logoutbtn:(id)sender
{
NSLog(#"logout is called");
[sessionTimer invalidate];
sessionTimer = nil;
[[BCDTimeManager sharedTimerInstance]stopIdleTimer];
//[self dismissViewControllerAnimated:YES completion:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"ApplicationTimeout" object:nil];
[self performSegueWithIdentifier:#"Thankyoupage" sender:self];
}
This is code where i posting the notification.
- (void)idleTimerExceeded {
NSLog(#"idle time exceeded");
[[NSNotificationCenter defaultCenter]
postNotificationName:#"ApplicationTimeout" object:nil];
}
for first time login, it works fine whenever timer exceeds, i post a notification and model view is presesnted perfectly, but once user logs out, after that whenever the notification is posted, selector method is getting called twice
I am pretty sure that notification is getting posted only once.
Should i create notification in every view controller and then remove it when view unloads?
what i am doing wrong here?
You are adding the notification in RootViewController and trying to remove it from LogoutViewController. So that notification observer added to the RootViewController never gets removed. So each time you logout and login, the observer call will get increased by one. For fixing the issue, you need to remove the observer from the RootViewController object.
For fixing the issue you mentioned in your comment,
If I remove the observer in RootViewController , then if timers
exceeds in some other views, and notification observer is not called.
Also, i can't add observer on app delegate because we want timer
notification to be fired only after reaching rootviewController
Write two public methods in AppDelegate
One for adding observer (addObserver)
One for removing observer (removeObserver)
When you reach RootViewController, call the addObserver method for adding the observer
When logout is pressed, call the removeObserver for removing the observer

NSViewController -- dismiss with memory cleanup

My environment is Yosemite 10.10.5 with Xcode 7.2 using ARC.
In a simple test program, I am attempting various ways to dismiss a NSViewController and all of them are showing problems with memory handling.
In my primary view controller, I have the following code. (The notification pieces are there to test various ways of dismissing the presented controller.)
- (IBAction)showFirstReplacement:(id)sender {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(dismissWithNotification:) name:#"removeFirst" object:nil];
NSStoryboard *sb = [self storyboard];
FirstReplacement *controller = [sb instantiateControllerWithIdentifier:#"first_replacement"];
[self presentViewControllerAsSheet:controller];
}
- (void)dismissWithNotification:(NSNotification *)notification {
NSViewController *controller = [notification object];
[self dismissViewController:controller];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
Inside FirstReplacement, I have:
- (IBAction)dismiss:(id)sender {
[self dismissViewController:self];
// [[NSNotificationCenter defaultCenter] postNotificationName:#"removeFirst" object:self];
// [[self presentingViewController] dismissViewController:self];
}
Uncommenting any one of the three lines in this method produces the correct visual results but.... Depending on which of the calls I enable inside dismiss:, I get different results when profiling. Using self dismissViewController:, I see no leaks but FirstReplacement objects are not deallocated. Using either of the other two approaches gets rid of the dismissed FirstReplacement but leaks one 16-byte malloc block and one NSMutableArray every time a view controller is dismissed.
According to Instruments, the leaks are related to a method called [NSViewController _addPresentedViewController:].
Are there other clean-up steps necessary to prevent these leaks (or memory bloat in the non-leak case)?
The view controller that presents another view controller is also responsible for dismissing it. So none of the lines in FirstReplacement's dismiss method are correct. Instead, you should be creating a delegate in FirstReplacement so it can notify its delegate (the primary view controller) that it should be dismissed.
FirstReplacement.h
#class FirstReplacement;
#protocol FirstReplacementDelegate <NSObject>
- (void)firstReplacementShouldDismiss:(FirstReplacement *)controller;
#end
#interface FirstReplacement : NSViewController
#property (nonatomic, weak) id<FirstReplacementDelegate> delegate;
#end
FirstReplacement.m
- (IBAction)dismiss:(id)sender {
[self.delegate firstReplacementShouldDismiss:self];
}
Then in your primary view controller:
- (IBAction)showFirstReplacement:(id)sender {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(dismissWithNotification:) name:#"removeFirst" object:nil];
NSStoryboard *sb = [self storyboard];
FirstReplacement *controller = [sb instantiateControllerWithIdentifier:#"first_replacement"];
controller.delegate = self;
[self presentViewControllerAsSheet:controller];
}
- (void)firstReplacementShouldDismiss:(FirstReplacement *)controller {
[self dismissViewController:controller];
}
While it may seem like posting a notification is the same as a delegate, it is not. The difference is that when dismissWithNotification fires, you are still executing the code from FirstReplacement::dismiss. NSNotificationCenter::postNotificationName does not finish executing until all observers have finished executing their selectors. So even though the dismissal code is executing in the primary view controller, it still being run from the dismiss method.
If you are still not convinced, override FirstReplacement::dealloc to print a log statement. You will see that dealloc is not called using any of your methods, but will be called using delegation.

Does NSManagedObjectContext's mergeChangesFromContextDidSaveNotification: post NSManagedObjectContextDidSaveNotification?

I have a bunch of NSOperations with their own NSManagedObjectContexts making changes to my Core Data store and saving and successfully getting their changes into the main thread's NSManagedObjectContext. This much I know. Now I want the front UIViewController to be notified when the main context is updated. So I...
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(refreshData:)
name:NSManagedObjectContextDidSaveNotification
object:[NSManagedObject mainThreadManagedObjectContext]];
}
However I don't think after merging changes from background threads the main thread's NSManagedObjectContext is posting any notifications of its own. I tried to find somewhere in the docs where Apple says I should post my own after telling the main thread to merge changes or some such, but no luck. For extra reference in my NSOperation I have...
+ (void)mergeChanges:(NSNotification *)notification
{
NSManagedObjectContext *managedObjectContext = [self mainThreadManagedObjectContext];
[managedObjectContext performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification:)
withObject:notification
waitUntilDone:YES];
}
+ (NSManagedObjectContext *)adHocManagedObjectContext
{
NSManagedObjectContext *adHocManagedObjectContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] adHocManagedObjectContext];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:adHocManagedObjectContext];
return adHocManagedObjectContext;
}
Should I expect to hear from the main thread's NSManagedObjectContext about it saving or should I post my own notification after mergeChangesFromContextDidSaveNotification:?
mergeChangesFromContextDidSaveNotification: does not post NSManagedObjectContextDidSaveNotification, because mergeChangesFromContextDidSaveNotification: does not tell the context to save.
Perhaps your front UIViewController should observe NSManagedObjectContextObjectsDidChangeNotification.

How to call a selector in a different class?

I'm trying to use this code but Xcode returns an error because the method I'm trying to call in the selector:#selector() is in another class. Thanks for your help!
AppDelegate.m:
-(void)applicationDidBecomeActive:(UIApplication *)application{
[..]
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(myMethodHere) name:UIApplicationDidBecomeActiveNotification object:nil];
}
MainViewController.m:
-(void)myMethodHere{
[..]
}
The problem is that you use
addObserver:self
which means that it looks for the function in the current class. Instead do something like
addObserver:instanceOfOtherClass
Update
Add the call to the init method of MainViewController
// MainViewController.m
- (id)init;
{
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(someMethod) name:UIApplicationDidBecomeActiveNotification object:nil];
}
return self;
}
Make sure to remove yourself in dealloc
- (void)dealloc;
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
By doing it this way from the very moment the object comes in to existence it is ready to receive notifications and then when it is being deallocated it will safely remove itself.
A good pattern to follow is to make the class that is doing the observing responsible for registering for notifications. This keeps encapsulation well and removes some risk of sending notification to deallocated instances.
Rationale
You need to balance your calls for registering for notifications and unregistering for notifications otherwise a message may be called on a deallocated object which could be hard to track down.
If I have a class that needs to be notified of an event the likely hood is I will register for the notifications in the init method and then unregister for the notifications in the dealloc (init and dealloc are just examples of times I often do this, not necessarily the best place in every example, do what makes sense in your case).
The issue is your use of
addObserver:self
The observer needs to be an instance class that contains the method you want to call, so create that first and then add the notification. Something like.
-(void)applicationDidBecomeActive:(UIApplication *)application{
[..]
SomeClass *newObject = [[SomeClass alloc] init];
[[NSNotificationCenter defaultCenter] addObserver:newObject selector:#selector(someMethodContainedInSomeclass) name:UIApplicationDidBecomeActiveNotification object:nil];
}

Cannot change views in splitviewcontroller

I am trying to change the views in a splitview controller based upon clicking a button in a modalview (a person is selecting an option). I am using notifications to accomplish this:
When the button is clicked in the modal view, it issues a notice, then closes (dismisses) itself:
[[NSNotificationCenter defaultCenter] postNotificationName:#"launchProject" object:nil];
The DetailViewController inside the split view controller is listening for this notification, and switches out the views in the SVC
-(void)launchProject:(NSNotification *)notification {
Project* secondDetail2 = [[Project alloc] initWithNibName:nil bundle:nil];
ProjectRootController* secondRoot2 = [[ProjectRootController alloc] initWithNibName:nil bundle:nil ];
self.splitViewController.viewControllers =[NSArray arrayWithObjects: secondRoot2, secondDetail2 , nil];
}
I don't understand why the views aren't switching out. Any advice on this will be welcome.
You haven't shown all the code, so I'm guessing the problem is a misunderstanding of how notifications work. It can be initially confusing, but it's very straightforward. So far, you have:
[[NSNotificationCenter defaultCenter] postNotificationName:#"launchProject" object:nil]
which is fine.
But you also need to have
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(launchProject:) // selector should be your function name, launchProject
name:#"launchProject" // notification name - must be same as what is given to postNotificatioName.
object: nil];
somewhere, like in an init function.
In other words, postNotificationName:#"launchProject" does NOT call your function launchProject. It puts a notification with the name "launchProject" into the NSNotificationCenter defaultCenter. If you're not looking for that particular notification, then nothing will happen.
Hope that helps..