NSManagedObjectContextDidSaveNotification called multiple times - ios

Hi I have a FriendsViewController where I display my friends records fetched from coreData. I have another View Controller AddFriendViewController Which is presented by FriendsViewController to add a new friend the and it saves the the Context in it. I am listening to this notification of changes on the shared MOC in my FriendsViewController.
[[NSNotificationCenter defaultCenter]
addObserverForName:NSManagedObjectContextDidSaveNotification
object:appdelegate.context queue:nil
usingBlock:^(NSNotification * _Nonnull note) {
NSLog(#"Re-Fetch Whole Friends Array from core data and Sort it with UILocalizedIndexedCollation and reloadData into table");
}];
In AddFriendsViewController just create a friend object and i
Friend *friend= [NSEntityDescription
insertNewObjectForEntityForName:#"Friend"
inManagedObjectContext:appdelegate.context];
friend.name=nameTextfield.text;
[appdelegate.context save:&error];
[self.navigationController popViewControllerAnimated:YES];
Now when I perform save on the context from a AddFriendViewController the above block in FriendsViewController is triggered couple of times instead of one time which cause more processing because re-fetching whole data from core data.I cannot use Fetched Results Controller because I am using UILocalizedIndexedCollation to sort my array into section. So my question is why it is being called twice or sometimes even thrice? Or is there any alternative for this ?

Only you know when you want the notification observer to be active.
However, two common paradigms are:
If you want to be notified anytime during the life of the view controller, then you register the observer in viewDidLoad and remove the observer in dealloc.
If you want to be notified anytime the view is active, you register the observer in viewWillAppear and remove in viewWillDisappear.
EDIT
I used this statement to remove all entries [[NSNotificationCenter
defaultCenter]removeObserver:self]; And it was still showing same
behaviour. Then I used addObserver: selector: name: object: method
which worked. But Why the other one was not removed ? – Asadullah Ali
That's because you added a block-based observer, which returns an observer object. You remove the object it returned to you. You really should read the documentation of each method that you use.
If you use the block-observer method, the add/remove will look like this.
id observer = [[NSNotificationCenter defaultCenter]
addObserverForName:SomeNotification
object:objectBeingObserved
queue:nil
usingBlock:^(NSNotification *note) {
// Do something
}];
[[NSNotificationCenter defaultCenter] removeObserver:observer];
If you use the selector-observer method, you need to remove the observer that you provide to the add call.
[[NSNotificationCenter defaultCenter]
addObserver:someObject
selector:#selector(notificationHandler:)
name:SomeNotification
object:objectBeingObserved];
[[NSNotificationCenter defaultCenter]
removeObserver:someObject
name:SomeNotification
object:objectBeingObserved];

First, I would strongly suggest using a NSFetchedResultsController instead of building your own observer.
Second, sounds like you are adding the observer several times. You should add it in the -viewDidLoad and remove it in -dealloc.
Again, a NSFetchedResultsController is a better solution. You will have better performance and avoid refetching like you are doing now.

You must get rid of the observer (as others already have stated here). The easiest way would be to use a "one-time observer" which removes itself when activated. In your example code this would be something like:
id __block observer;
observer = [[NSNotificationCenter defaultCenter]
addObserverForName:NSManagedObjectContextDidSaveNotification
object:[PubStore sharedStore].managedObjectContext queue:nil
usingBlock:^(NSNotification * _Nonnull notification) {
[[NSNotificationCenter defaultCenter] removeObserver:observer];
NSLog(#"Re-Fetch Whole Friends Array from core data and Sort it with UILocalizedIndexedCollation and reloadData into table");
}];
Note that you must store the observer in a __block variable to be able to use it inside the block executed when the observer is triggered.

Related

Object parameter in method postNotification of NSNotificationCenter

In my iOS application, I am posting a NSNotification and catching it in one of my UIView in main thread. I want to pass extra information along with the notification. I was using userInfo dictionary of NSNotification for that.
[[NSNotificationCenter defaultCenter] postNotificationName:#"NotifyValueComputedFromJS" object:self userInfo:#{#"notificationKey":key,#"notificationValue":value,#"notificationColor":color,#"notificationTimeStamp":time}];
key, value, color and time are local variables which contains the value I need to pass. In UIView I am adding observer for this notification and I am using notification.userInfo to get these data
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotification:) name:#"NotifyValueComputedFromJS" object:nil];
-(void)receiveNotification:(NSNotification *)notification
{
if ([notification.userInfo valueForKey:#"notificationKey"]!=nil && [[notification.userInfo valueForKey:#"notificationKey"] isEqualToString:self.notificationKey] && [notification.userInfo valueForKey:#"notificationValue"]!=nil) {
[self updateLabelWithValue:[notification.userInfo valueForKey:#"notificationValue"]];
}
}
The frequency in which this notification is posted is 4 times in one second. I am doing some animations also in main thread. The problem I am facing here is my UI is lagging. UI will respond to scroll events or touch events with huge delay(I have faced a delay of even 1 to 2 seconds). After some research I came to know that NSDictionary is bulky and will cause lag if used in main thread. Is there any other way I can pass my data through NSNotification?
I have tried out another way. I have created a custom NSObject class to save the data I want and I am passing it as the object parameter of postNotification method.
[[NSNotificationCenter defaultCenter] postNotificationName:#"NotifyValueComputedFromJS" object:customDataObject userInfo:nil];
Here customDataObject is an instance of my custom NSObject class. I know the parameter is meant to be the sender of notification(usually it will be self). Is it a wrong approach if I am sending a custom object as parameter?
As BobDave mentioned, the key is to send the notification on some thread other than the main UI thread. This can be accomplished with dispatch_async, or with a queue.
The typical pattern for this behavior is sender:
-(void)sendDataToObserver {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:#"NotifyValueComputedFromJS" object:customDataObject userInfo:userInfo:#{#"notificationKey":key,#"notificationValue":value,#"notificationColor":color,#"notificationTimeStamp":time}];
});
}
And receiver (NOTE: weak self because retain cycles):
-(void)addObserver {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotification:) name:#"NotifyValueComputedFromJS" object:nil];
}
-(void)receiveNotification:(NSNotification *)notification {
if ([notification.userInfo valueForKey:#"notificationKey"]!=nil && [[notification.userInfo valueForKey:#"notificationKey"] isEqualToString:self.notificationKey] && [notification.userInfo valueForKey:#"notificationValue"]!=nil) {
__weak typeof (self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf updateLabelWithValue:[notification.userInfo valueForKey:#"notificationValue"]];
});
}
}
Maybe you could use - addObserverForName:object:queue:usingBlock:
and use a non-main queue to execute the block in order to reduce the lag. Also, shouldn't the observer be added in a UIViewController, not a UIView?

Efficiently register multiple observes to a notification

I'm writing an iOS game with a bunch of different types of enemies and items/drops. Specific ones need to trigger things on different events, so I'm experimenting with using NSNotificationCenter.
Everything works when listener is defined in a way that's retained. When I define it locally or add it to an NSMutableArray, it's lost and the postNotification throws a EXC_BAD_ACCESS
This breaks, because the listener isn't retained past this.
MyListener *listener = [[MyListener alloc] init];
[[NSNotificationCenter defaultCenter] addObserver:listener selector:#selector(myEventHandler:) name:#"MyEvent" object:nil];
This is a super basic example of what works:
MyListener *listener;
-(id)init {
listener = [[MyListener alloc] init];
[[NSNotificationCenter defaultCenter] addObserver:listener selector:#selector(myEventHandler:) name:#"MyEvent" object:nil];
}
The issue is that I will have a bunch of different enemy/item classes that need to listen for an event. I don't want a different class-level variable for every single one - but admit that I may be going about this the wrong way. I'm still somewhat new to iOS.
Well, first of all, your init snippet is entirely wrong. init methods should look more like this:
- (instancetype) {
self = [super init];
if (self) {
// initialize
}
return self;
}
And in your example, // initialize would be replaced with:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(myEventHandler:)
name:#"MyEvent"
object:nil];
But what's most important here is that at some point before this object is completely deallocated, we must stop observing. If we do not, after this object is deallocated, NSNotificationCenter will try to send a message to a deallocated object, which causes your EXC_BAD_ACCESS.
You may wish to stop observing earlier than dealloc, but at a minimum, you need to add this to your class:
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
As for keeping the object alive, well that's as simple as keeping a strong reference to it. This is sort of an entirely different question. Although... if you're trying to keep an object alive simply to respond to a notification, there's no need to even use an object here.
You can give the notification center a block to respond to notification with using the following method:
- (id)addObserverForName:(NSString *)name
object:(id)obj
queue:(NSOperationQueue *)queue
usingBlock:(void (^)(NSNotification *))block

How to tell a viewcontroller to update its UI from another class

I am trying to understand how updating of a viewController that is currently visible works.
I have ViewControllerA and ClassA. I want to tell ViewControllerA to reloadData on the tableview from ClassA. What is the best way to go about doing this?
I have found this question and answer but I dont think this will work in my situation or I am not understanding it correctly.
The easiest way without knowing your setup would be to use NSNotificationCenter. Here is what you could do:
In ViewControllerA add in hooks for NSNotificationCenter:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
//Register for notification setting the observer to your table and the UITableViewMethod reloadData. So when this NSNotification is received, it tells your UITableView to reloadData
[[NSNotificationCenter defaultCenter] addObserver:self.table selector:#selector(reloadData) name:#"ViewControllerAReloadData" object:nil];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
//Need to remove the listener so it doesn't get notifications when the view isn't visible or unloaded.
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
Then in ClassA when you want to tell ViewControllerA to reload data just post the NSNotification.
- (void)someMethod {
[[NSNotificationCenter defaultCenter] postNotificationName:#"ViewControllerAReloadData" object:nil];
}
Answers here about using NSNotificationCenter are good. Be aware of a few other approaches:
A common one is the delegate pattern (see here and here).
Another is that the view controller observes the model change using KVO. (see here and here).
Another good one, often overlooked, which can probably be used in your case is the "do almost nothing" pattern. Just reloadData on your table view when viewWillAppear.
Key value Coding , NSNotificationCentre and Delegates are preferred. But NSNotificationCentre is easiest in your case.
The UIViewController that contains UITableView must add observer like this :
Init :
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(methodToReloadTable:)
name:#"TABLE_RELOAD"
object:nil];
In delloc method :
[[NSNotificationCenter defaultCenter] removeObserver:self];
Post it from any XYZ class like on any UIButton action :
[[NSNotificationCenter defaultCenter]
postNotificationName:#"TABLE_RELOAD"
object:self];
Advantage of NSNotificationCentre is that they can add observers in multiple classes ..

Why is my NSNotification its observer called multiple times?

Within an App I make use of several viewcontrollers. On one viewcontroller an observer is initialized as follows:
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"MyNotification" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(myMethod:) name:#"MyNotification" object:nil];
Even when removing the NSNotification before initializing the number of executions of myMethod: is being summed up by the amount of repeated views on the respective viewcontroller.
Why does this happen and how can I avoid myMethod: being called more then once.
Note: I made sure by using breakpoints that I did not made mistakes on calling postNotification multiple times.
Edit: This is how my postNotification looks like
NSArray * objects = [NSArray arrayWithObjects:[NSNumber numberWithInt:number],someText, nil];
NSArray * keys = [NSArray arrayWithObjects:#"Number",#"Text", nil];
NSDictionary * userInfo = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
[[NSNotificationCenter defaultCenter] postNotificationName:#"myNotification" object:self userInfo:userInfo];
edit: even after moving my subscribing to viewwillappear: I get the same result. myMethod: is called multiple times. (number of times i reload the viewcontroller).
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"MyNotification" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(myMethod:) name:#"MyNotification" object:nil];
}
edit: something seems wrong with my lifecycle. ViewDidUnload and dealloc are not getting called, however viewdiddisappear is getting called.
The way I push my Viewcontroller to the stack is as follows where parent is a tableview subclass (on clicking the row this viewcontroller is initiated:
detailScreen * screen = [[detailScreen alloc] initWithContentID:ID andFullContentArray:fullContentIndex andParent:parent];
[self.navigationController pushViewController:screen animated:YES];
Solution:
Moving removal of nsnotification to viewdiddisappear did the trick. Thanks for guidance!
Based on this description, a likely cause is that your viewcontrollers are over-retained and not released when you think they are. This is quite common even with ARC if things are over-retained. So, you think that you have only one instance of a given viewcontroller active, whereas you actually have several live instances, and they all listen to the notifications.
If I was in this situation, I would put a breakpoint in the viewcontroller’s dealloc method and make sure it is deallocated correctly, if that’s the intended design of your app.
In which methods did you register the observers?
Apple recommends that observers should be registered in viewWillAppear: and unregistered in viewWillDissapear:
Are you sure that you don't register the observer twice?
Ran into this issue in an application running swift. The application got the notification once when first launched. the notification increases the number of times you go into the background and come back. i.e
app launches one - add observer gets gets called once in view will appear or view did load - notification is called once
app goes into background and comes back, add observer gets called again in view will appear or view did load. notification gets called twice.
the number increases the number of times you go into background and come back.
code in view will disappear will make no difference as the view is still in the window stack and has not been removed from it.
solution:
observe application will resign active in your view controller:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "applicationWillResign:", name: UIApplicationWillResignActiveNotification, object: nil)
func applicationWillResign(notification : NSNotification) {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
this will make sure that your view controller will remove the observer for the notification when the view goes into background.
it is quite possible you are subscribing to the notifications
[[NSNotificationCenter defaultCenter] postNotificationName:#"myNotification" object:self userInfo:userInfo];
before self gets initialized. And trying to unsubscribe 'self' which isn't really subscribed to, and you will get all global myNotification notifications.
If your view was hooked up in IB, use -awakeFromNib: as the starting point to register for notifications
It is possible that the class with the observer is, quite appropriately, instantiated multiple times. When you are debugging it will kinda look like the notification is being posted multiple times. But if you inspect self you might see that each time is for a different instance.
This could easily be the case if your app uses a tab bar and the observer is in a base class of which your view controllers are subclasses.

NSnotification center not working iOS

Last week I asked this question: Refresh the entire iOS app
#Jim advised me to use the notification center. I was not able to figure out how to work with notifications and I was told to ask another question, I tried for the full week to figure it out on my own, so here goes.
I have a view with multiple subviews. One of the subviews is a search bar (not the tableview one, just a custom text box), the user can search for a new person here and the entire app will be updated screen by screen.
When the user taps on the GO button in the search subview I make the call to the server to get all the data. After which I post this notification:
[self makeServerCalls];
[[NSNotificationCenter defaultCenter] postNotificationName:#"New data" object:Nil];
Now in the init of my parent view controller I have a listener
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(viewDidLoad) name:#"New data" object:nil];
I know this is most probably wrong, so can anyone explain to me how to use notifications properly in my situation? Or if there is a better way of doing what I want.
Thanks for any help you can give me.
When you post a notification, it will cause all register observers to be notified. They get notified by having a message sent to them... the one identified by the selector. As mentioned in the comments, you should not use viewDidLoad. Consider this...
- (void)newDataNotification:(NSNotification *notification) {
// Do whatever you want to do when new data has arrived.
}
In some early code (viewDidLoad is a good candidate):
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(newDataNotification:)
name:#"New data"
object:nil];
That's a terrible name, BTW. Oh well. This registration says that your self object will be sent the message newDataNotification: with a NSNotification object whenever a notification is posted with the name "New data" from any object. If you want to limit which object you want to receive the message from, provide a non-nil value.
Now, when you send the notification, you can do so simply, like you did in your code:
[[NSNotificationCenter defaultCenter] postNotificationName:#"New data" object:nil];
and that will make sure (for practical purposes) that [self newDataNotification:notification] is called. Now, you can send data along with the notification as well. So, let's say that the new data is represented by newDataObject. Since you accept notifications from any object, you could:
[[NSNotificationCenter defaultCenter]
postNotificationName:#"New data"
object:newDataObject];
and then in your handler:
- (void)newDataNotification:(NSNotification *notification) {
// Do whatever you want to do when new data has arrived.
// The new data is stored in the notification object
NewData *newDataObject = notification.object;
}
Or, you could pass the data in the user info dictionary.
[[NSNotificationCenter defaultCenter]
postNotificationName:#"New data"
object:nil
userInfo:#{
someKey : someData,
anotherKey : moreData,
}];
Then, your handler would look like this...
- (void)newDataNotification:(NSNotification *notification) {
// Do whatever you want to do when new data has arrived.
// The new data is stored in the notification user info
NSDictionary *newData = notification.userInfo;
}
Of course, you could do the same thing with the block API, which I prefer.
Anyway, note that you must remove your observer. If you have a viewDidUnload you should put it in there. In addition, make sure it goes in the dealloc as well:
- (void)dealloc {
// This will remove this object from all notifications
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

Resources