I have a class Poster which posts notifications through the NSNotificationCenter. I have two different observers: ObserverSuperclass and ObserverSubclass. ObserverSuperclass is the superclass of ObserverSubclass. I'd like each class to respond differently to the notification.
According to NSHipster, I should use the modern block-based api: addObserverForName:object:queue:usingBlock:.
In ObserverSubclass's initialization method, I need to remove the superclass as an observer. Because I'm using the block-based API, I need to reference the return value of addObserverForName:object:queue:usingBlock: - an "opaque object to act as the observer". So I write the following code:
In ObserverSuperclass.h
#property (nonatomic, strong) id observer;
In ObserverSuperclass.m
self.observer = [NSNotificationCenter defaultCenter] addObserverForName:#"Help!" object:nil queue:nil usingBlock:^{old block}];
In ObserverSubclass.m
[[NSNotificationCenter defaultCenter] removeObserver:self.observer name:#"Help!" object:nil];
self.observer = [NSNotificationCenter defaultCenter] addObserverForName:#"Help!" object:nil queue:nil usingBlock:^{new block}];
Is this actually the best way to do this? I'm not sure it makes sense to use the block-based API here.
This is a case where, in my opinion, you should use the traditional #selector callbacks instead. Your superclass can register the observation and implement the default behavior. The subclass then needs only override the called back #selector.
The method that you should implement is:
- (void)addObserver:(id)notificationObserver
selector:(SEL)notificationSelector
name:(NSString *)notificationName
object:(id)notificationSender
For example, in the superclass:
- (instancetype) init
{ ...
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(oberserveThis:)
name:#"someEventName"
object:nil];
}
- (void)observeThis:(NSNotification*)notification
{
// ... Do something here ...
}
In your subclass:
- (void)observeThis:(NSNotification*)notification
{
// ... Do something else here ...
}
If you need the code in the observer to execute on a specific queue you can always call dispatch within the observation selector. I think that, for your use case, it makes for much cleaner code than using the block based approach.
Your code looks fine.
The logic you've mapped out enforced the idea that you only want one observer, either the superclass or the subclass. Your subclass' observer removes and replaces the superclass' observer.
It's also valid to have them both observing the same notification name. In that case you'd add a new instance variable self.newObserver to the subclass and add an observer to that property as well. Then both blocks would get invoked when a "Help!" notification is posted.
Which you do depends on what you're trying to accomplish.
You DO need to remove an observer when you don't need it any more, or you risk crashing when the objects the observer block deals with get deallocated out from under it. (This is less likely with block-based observers than with selector-based observers, because the notification center retains the block automatically, and even if the object that adds the observer gets deallocated the observer block will still run. With a selector based observer you MUST remember to remove your observer in your dealloc method or you crash after your object is deallocated if the type of notification you are observing gets posted again.)
Related
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.
Normally I add my observers in viewWillAppear and remove them in viewWillDisappear. In this case I need one of the observers to continue even after the view is gone so that it can finish some work. In order to make sure that the observer is only added once with this view, I do the following:
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:YES];
[[NSNotificationCenter defaultCenter]removeObserver:self
name:#"imageSaved"
object:nil];
[[NSNotificationCenter defaultCenter]addObserver:self
selector:#selector(postMessageWithImage:)
name:#"imageSaved"
object:nil];
}
I have performed a search through the rest of the application to ensure that this observer is NOT registered anywhere else. Unfortunately sometimes, but not all times and there is no consistent factor, the notification is fired twice. I have also ensured with breakpoints and NSLog that the postNotifcationName is NOT called more than once. I have not been able to reproduce on the iPhone as the problem seems confined to the iPad.
In further troubleshooting I have checked that the method is being called from the same thread (no reason it wouldn't be but just to check). This problem DOES go away if I put the removeObserverin viewWillDisappear, however, again that is not how I need this to work.
Clearly this is a case where the observer for this is being registered twice but I cannot find a reason why that would be. As you can see from the code, any time this observer is registered it is first removed. My only other thought is whether self could get "corrupted" such that the removeObserverwouldn't function properly?
Add your observer when the view will show, and remove it when will disappear.
ADD:
- (void)viewDidLoad
{
[[NSNotificationCenter defaultCenter]addObserver:self
selector:#selector(postMessageWithImage:)
name:#"imageSaved"
object:nil];
}
REMOVE:
- (void)postMessageWithImage:(NSNotification*)aNotification
{
[[NSNotificationCenter defaultCenter]removeObserver:self
name:#"imageSaved"
object:nil];
// here do your job
}
This is perfectly valid and efficient.
Instead of adding the observer in viewWillAppear:, try adding the observer you wish to persist when the view disappears in viewDidLoad. Then you can call your removeObserver:name:object: in your dealloc method
If you just want something to be executed once, put it in the predicate of a dispatch_once() call, like
static dispatch_once_t lock;
dispatch_once(&lock, ^{
// put your addObserver call here
});
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
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.
App delegate:
- (void)applicationDidBecomeActive:(UIApplication *)application {
[[NSNotificationCenter defaultCenter] postNotificationName:APP_REFRESH_NOTIFICATION object:nil];
}
In my view controller:
- (void)viewDidLoad {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(doStuff) postNotificationName:APP_REFRESH_NOTIFICATION object:self];
}
- (void)doStuff never gets called. Why?
I assume that you've typed your question incorrectly and you'd meant to write addObserver:selector:name:object:, instead of addObserver:selector: postNotificationName:object: (such method doesn't exist).
In the documentation of - (void)addObserver:(id)notificationObserver selector:(SEL)notificationSelector name:(NSString *)notificationName object:(id)notificationSender
we can read:
notificationSender
The object whose notifications the observer wants
to receive; that is, only notifications sent by this sender are
delivered to the observer. If you pass nil, the notification center
doesn’t use a notification’s sender to decide whether to deliver it to
the observer.
So in your case, as you're passing object:nil in postNotificationName:object:, you also have to set object:nil in addObserver:selector:name:object:.
According to the documentation you also should replace the method doStuff with:
- (void)doStuff:(NSNotification *)notification
and use #selector(doStuff:) in addObserver:selector:name:object:.
You're passing self as the object parameter to addObserver:selector:name:object:, but doStuff doesn't accept any parameters, so the method call fails (silently). Your viewDidLoad should look like this:
- (void)viewDidLoad {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(doStuff)
name:APP_REFRESH_NOTIFICATION
object:nil];
}
You're app delegate is posting a notification when the app becomes active, but your view controller isn't subscribing to that until its view gets loaded. If your app delegate is creating your view controller and loading it (which is probable) then your controller doesn't even exist at the time the notification is posted, which is why it isn't receiving it. If you use a storyboard, and that controller is the entry point in the storyboard, AND you use the info.plist for your app to set that storyboard as the main interface, then it will have already instantiated the controller and loaded its view by the time -applicationDidBecomeActive: is called, solving your problem.