NSNotification is being called multiple times from

2019-02-11 07:40发布

问题:

I have a UITabBarController, which has 4 tabs. Each one of those tabs is a separate UIViewController. I have objects on each one of those 4 VC's that use NSNotification's to perform actions upon the press of a certain object. The 4 VC's all respond to the notification in the same way because it is a similar object on each page. When this object is pressed it presents a view onto the current view controller. The problem is that if I move to any of the other 3 tabs now that view is also on their VC. That is because the notification is being responded to on all 4 tabs when it is pressed on any of the VC's. I am needing it to only respond to the VC that the user is currently on and not any of the others that are in the tab bar.

Is there a way to get this to work properly? Maybe a threshold where you can set how many times the notification can perform its selector after being called? That way I could set it to 1 and at any given time if that notification is called the selector can only be called 1 time.

The type of object implementation that I'm using requires me to use NSNotification's so there is no way to change how I interact.

edit:

This viewDidLoad method is on the top level VC for the 4 VC's in my tab bar. All 4 of them either use this directly or inherit from it.

- (void) viewDidLoad
{
    ...
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didSelectItemFromCollectionView:) name:@"didSelectItemFromCollectionView" object:nil];
}

Action Handler:

- (void) didSelectItemFromCollectionView:(NSNotification *)notification
{
    NSDictionary *cellData = [notification object];

    if (cellData)
    {
        NewVC *pushToVC = [self.storyboard instantiateViewControllerWithIdentifier:@"PushToVC"];

        [self.navigationController pushViewController:pushToVC animated:YES];
    }
}

Each of the 4 VC's is a UITableViewController and have cells with an object that can be pressed. This NSNotificationCenter action is what allows the operation to work.

回答1:

You must have implemented the NSNotificationCenter's -addObserver:selector:name:object: method in the -viewDidLoad of every viewController

Example:

- (void)viewDidLoad
{
    //...
    [NSNotificationCenter defaultCenter] addObserver:self
                                            selector:@selector(doSomething:) 
                                                name:@"TestNotification"
                                              object:nil];
}

Instead of having this in -viewDidLoad, move it within -viewWillAppear and implement removeObserver:name:object: in -viewWillDisappear.

This way, only the viewController that is currently on will respond to the notification.

Example:

- (void)viewWillAppear:(BOOL)animated
{
    //...
    [NSNotificationCenter defaultCenter] addObserver:self
                                            selector:@selector(doSomething:) 
                                                name:@"TestNotification"
                                              object:nil];
}

- (void)viewWillDisappear:(BOOL)animated
{
    //...
    [NSNotificationCenter defaultCenter] removeObserver:self
                                                   name:@"TestNotification"
                                                 object:nil];
}

- (void)doSomething:(NSNotification *)userInfo
{
    //...
    //if you push a viewController then the following is all you need
    [self.navigationController pushViewController:vcSomething
                                         animated:YES];

    //however.... if you're instead presenting a viewController modally then
    //you should implement "-removeObserver:name:object: in this method as well

    //[NSNotificationCenter defaultCenter] removeObserver:self
    //                                               name:@"TestNotification"
    //                                             object:nil];
    //[self presentViewController:vcSomething
    //                   animated:YES
    //                 completion:nil];

    //OR... in the completion parameter as:

    //[self presentViewController:vcSomething
    //                   animated:YES
    //                 completion:^{
    //                     [NSNotificationCenter defaultCenter] removeObserver:self
    //                                                                    name:@"TestNotification"
    //                                                                  object:nil];
    //                 }];
}

EDIT:

You (@Jonathan) commented:

I really appreciate your answer and it has helped me out a lot! I actually ran into one more scenario where this issue occur's and I'm not sure how to figure it out. Right now I have a VC that presents another VC modally. Each one has observers for the same NSNotification. Everything performs perfectly well when I'm in the modally presented VC, but once I dismiss that VC and return to the underlying one I have the same issue where the notification is being called multiple times. Do you have an idea for a solution in this case?

Now... regarding this...

FIRSTLY... NOTE:

  1. Multiple -addObserver:selector:name:object: will register the specified notification multiple times (means... same notification being registered for N times will call invoke the target selector N times)
  2. Presenting a ViewController (call it Child) from, say, Parent viewController will NOT invoke the -viewWillDisappear: of the Parent
    where as...
  3. Dismissing the Child viewController will still invoke -viewWillAppear: of the Parent

This creates an imbalance in the logic and if not handled (as per the commented lines in the code example of the doSomething method above), it results in the Parent registering for the notification multiple times (as it's -viewWillAppear: method is called more often than not -viewWillDisappear:)

also see: similar question



回答2:

it should be called only once so that it never gets called again

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(methodName:) name:@"name" object:nil];
});