Lets say I have a simple object that represents the state of a puzzle. The puzzle can be either solved or unsolved. I have two controllers with references to this puzzle object and they are visually representing the state in two different ways - say an on/off switch and a red/green light.
When the 'solved' property of the puzzle changes, the controllers need to update their view to represent the current state. Is there a standard idiom for communication the state change from the puzzle object to the controller?
My initial intent was to declare a custom protocol and track observers in the puzzle object. When the solved property changed, iterate over all the observers and invoke a specific method on the protocol. This seems like a common enough pattern that there might be some built in support but I wasn't able to find exactly what I was looking for in the docs.
While both answers so far have concentrated on the use of NSNotification, and that's totally valid, there's another way to do what you want that's built-in to Cocoa objects: Key-Value Observing, or KVO. It's slightly lighter-weight and a little less "action at a distance". I prefer using it whenever possible for observing changes in my data model classes. YMMV.
If I'm understanding correctly you can use NSNotification to do what you want. In your puzzle class you can use postNotificationName to tell any class that's observing when the puzzle changes state. To register a class as an observer to the puzzle, use the addObserver and removeObserver methods. Here are the definitions for the three methods:
-(void) postNotificationName:(NSString *) aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo;
-(void) addObserver:(id)observer selector:(SEL)aSelector name:(NSString *)aName object:(id)anObject;
-(void) removeObserver:(id)observer name:(NSString *)aName object:(id)anObject;
Here is some example code to use these for your program:
In your puzzle class, in the function that changes state:
[[NSNotificationCenter defaultCenter] postNotificationName:#"puzzleChangedState" object:self userInfo:NULL]
// if you want to send out moreInfo, like other variables, use userInfo with a dictionary object
In your controllers, or views, etc... wherever you want to get the puzzle changed state message:
//In your constructor or initialization method, register this class with the puzzle class
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handlePuzzleChangedState:) name:#"puzzleChangedState" object:nil];
This will add your controller to the NotificationCenter and when the puzzle class posts a notification of "puzzleChangedState", your controller's handlePuzzleChangedState: method will be invoked.
Here is the handlePuzzleChangedState: function:
-(void) handlePuzzleChangedState:(NSNotification *) notification
{
//handle your puzzle state change here
}
If you want more help here's the docs:
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Notifications/Introduction/introNotifications.html#//apple_ref/doc/uid/10000043i
Hope it works out!
Instead of a custom protocol, you can use notifications as follows.
In your controller, in viewDidLoad register yourself as an observer
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(puzzleStateDidChange:)
name:#"PuzzleStateDidChange" object:nil];
then implement
- (void)puzzleStateDidChange:(NSNotification *)notification
{
// set your switch and light according to the state notified
puzzleState = notification.object;
...
}
and finally add
[[NSNotificationCenter defaultCenter] removeObserver:self];
to the dealloc method to unregister yourself as an observer.
Now you are ready to receive notifications and react accordingly. What is missing is the code to be added to your puzzle object. Each time the state changes in the puzzle object,
use something like
[[NSNotificationCenter defaultCenter] postNotificationName:#"PuzzleStateDidChange" object:yourPuzzleState];
to notify your controllers about the state change.
Related
I used to addObserver in viewDidLoad: and removeObserver in dealloc:. Code:
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(refreshData)
name:AnyNotification
object:nil];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self
name:AnyNotification
object:nil];
}
But according to some articles said, it's better to addObserver in viewDidAppear: and removeObserver in viewDidDisappear:. Code:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(refreshData)
name:AnyNotification
object:nil];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:AnyNotification
object:nil];
}
So, what's the better way to addObserver/removeObserver?
this depends on your scenario, usually the best approach is to add in viewDidLoad and remove in dealloc and in viewDidUnload (deprecated in iOS 9.0, use dealloc only), but there are some cases when you have same method in different classes like UI effects and want to call only current screen's method using notification, then you will have to add the observer in viewWillAppear and remove it in viewWillDisappear or viewDidAppear/viewDidDisappear
Edit:
A note from comments, thanks #honey.
Though now since iOS 9, you no longer need to care about removing the observer. See Apple release notes: "In OS X 10.11 and iOS 9.0 NSNotificationCenter and NSDistributedNotificationCenter will no longer send notifications to registered observers that may be deallocated..
I would normally put it in -viewDidAppear: and -viewDidDisapear: (or -viewWillAppear: and -viewWillDisappear:) simply because in every case I came across I'm only interested in the notification if the view is actually displayed.
It's probably a premature optimisation (your code for handling the notification could take some time, but might be useless if the view is not displayed), but then it's also no more code - it's the same code just in a different method...
Don't forget NSKeyValueObservingOptionInitial. I use it with viewWillAppear/viewWillDisappear so my UI is always up-do-date, even if I hide that view controller, saving resources because I will not update it until is shown again.
The best approach using NSNotifications is adding the observer when you need to observe for notifications and remove them when you don't need them anymore.
This could be on viewDidLoad:, viewWillAppear:, or when the user taps some button etc.
I will give you a little example:
My app has a tabbar, and in some of the view controllers, I'm displaying some info downloaded from internet (a tweet for example). I also have a class pooling for new data from server each 2 minutes, and as the server had new data, I updated the info on database. I will not use a delegate pattern to listen to DB changes, because I have so many view controllers displaying data, and it will be a very bad design making the delegate an array and looping to pass the data for every view controller. So, in this specific scenario, the best to do is to post a notification telling every VC that new data has come.
If your VC removes the delegate when the view disappears, only the current one will receive the notification and update the displaying contents.
You obviously could update the contents of the other VCs before display, on viewWillAppear: for example, but doing this the VC contents will be updated not only when necessary, but each time you change tabs.
It was only one example, where I tried to show you that for NSNotifications, is difficult to advise you when to add or remove observers when we don't have the entire description of how you app behaviours.
-viewWillAppear: + -viewWillDisappear: is better than -viewDidAppear: + -viewDidDisapear:, because they are always called the same number of times.
I have an application where I used a UITabBarController with 3 buttons. So I also have 3 classes. What I want to do is to call an - (IBAction) doSomething: (id) sender {} in class 1 (view 1) with a button in class 2 (view 2).
Take whatever it is that your doSomething method (not function) does and use it to create method in a new class. Both controllers can import the class, instantiate it, and use the method.
Alternately you can send a notification to whichever controller has doSomething, but if the code in the method really does apply to both controllers, provide it to both controllers.
You can have one controller send a notification to another. When you want to notify class 1 to perform the button-pressed code you'll send out a notification like this:
[[NSNotificationCenter defaultCenter] postNotificationName:#"ABCPerformButtonAction"
object:nil];
You don't have to call it ABCPerformButtonAction, you just need a string that you'll recognize and something -- I used ABC because I don't know your initials or the name of the app or whatever -- to help ensure you don't accidentally send a notification that has the same name as a notification something you're unaware of is listening for (including 3rd party libraries you're using, etc.).
When that notification goes out, any object that has registered with the defaultCenter to listen for #"ABCPerformButtonAction" will perform any actions you choose. Here's how controller 1 registers (this should be located in some place like ViewDidLoad or the initialization method of the object):
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(performDoSomething:)
name:#"ABCPerformButtonAction"
object:nil];
The selector there, performDoSomething:, is just the name of a method you want to run when the notification goes out. That method has to have a specific format, so you can't call your doSomething method directly. It would look like this:
- (void)performDoSomething:(NSNotification *)notif {
[self doSomething];
}
As you can see, all it does is call the method. Obviously it could do much more, and you can even send information along with the notification (see below).
Lastly, it's important that you also remove your object as an observer before it's deallocated. In your Dealloc method of each object that registered to receive the notification you add this:
[[NSNotificationCenter defaultCenter] removeObserver:self];
Hopefully that makes sense. The Apple documentation for NSNotificationCenter explains more and they provide several sample apps that use notifications.
here i am again:
what i want to do is:
if i press a button, then post a notification. This notification should be cached by 2 instances of the same class.
the problem:
the notification is posted, but it is cached just by one instance.
some code and explanation
i have 1 tab bar controller
i have 3 tabs ( 3 different views -xib files-)
2 views references the same (view controller) class (so, there are 2 instances of the same class, let's say class A)
the other tab/view references another class (class B)
if i press a button of one view, a method of class B is fired and, at some point it does this:
[[NSNotificationCenter defaultCenter] postNotificationName:#"update" object:nil ];
in the viewDidLoad method of class A I have this:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateAll:) name:#"update" object:nil];
also, i have defined the updateAll function as:
- (void) updateAll: (NSNotification *) notification {
NSLog(#"called");
}
As i said before, just one time the updateAll method is fired.
questions
why?
how to fix it?
thanks for reading!
It is possible that your view is not loaded yet, because you are using tab bar controller. The view that is not yet visible is not loaded, so it is likely that your viewDidLoad will get called only for one instance. I recommend you debug it and make sure your addObserver call is really get executed twice, not once.
This won't work at all. You're posting a notification with a name #"updated" but you've attached observers for name #"update". You should be getting no notifications at all.
The way of posting notification is synchronous. I think another object doesn't register as an observer yet, so it cannot receive the notification posted.
And, if the notification is posted on another thread, it will be obtained by the observer on the same thread.
What are the possible ways to send data to previous view in iphone. Without using Appdelegate. Because there are chances for my view class to be instantiated again.
I believe the best approach is using the NSNotificationCenter class.
Basically what you do is register an object (as an observer) with a notification center.
So for example if you have objects A and B. A registers as an observer. Now lets say A is the "previous" object you are talking about, you can have B send a notification (data or message) to the notification center which then notifies object A (and any other registered observers).
Example:
In file ClassA.m register as shown below:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didSomething:) name:#"SomethingHappened" object:nil];
didSomething is the method which receives the notification sent by object B. This will look something like
- (void) didSomething: (NSNotification *) notify {
...
}
Finally you send the message below from whatever method in ClassB.m to notify/send data to object A
[[NSNotificationCenter defaultCenter] postNotificationName:#"SomethingHappened" object:self userInfo:your_data];
Seems convoluted but it's the best approach in my opinion (and quite simple once you understand it :)).
There are several ways to achieve data sharing, with Singleton Objetcs being one of the most popular:
Objective C Singleton
If the view you want to communicate with is a parent view (e.g. the previous view's view controller is where you created this view) then you probably want to handle dismissing the view in the previous view controller. When you do that, you can read the data that has changed and update the previous view controller with the new data.
Then in the viewWillAppear: method of the previous view controller, update the actual views to reflect the current state of the view controller.
Edit: I've just noticed that your newView is transparent. If this is the case, then you certainly want to route all logic through your view controller. You should only have one view controller with visible views at a time.
This isn't a question so much as a warning to others to save them some time.
NSNotificationCenter on iOS 3/iPhone OS 3 (I'm assuming also Mac OS X and iOS 4) has the following behavior:
If you register yourself multiple times for the exact specific notification, NSNotificationCenter will NOT recognize the redundancy and instead will fire off as many notifications to you as you've registered an observation for.
This is almost never the behavior you want to see and is almost always accidental.
Example:
I want my view controller to receive notifications from a singleton network object when new data comes in:
- (void) viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(newDataArrived:)
name:NewDataArrivedNotification
object:[NetworkListener sharedNetworkListener]];
}
but earlier I'd already put the same thing in viewWillAppear:
- (void) viewWillAppear
{
[super viewWillAppear];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(newDataArrived:)
name:NewDataArrivedNotification
object:[NetworkListener sharedNetworkListener]];
}
Note that it's exactly the same notification, resolving to the same observer, sender and notification name.
In this case, if I don't remove one of those addObserver calls, I'll get duplicate notifications to my view controller.
In a multi-threaded environment, this is a world of hurt. Trust me.
Just putting this out there in case there are others who run into something like this.
NSNotificationCenter on iOS 3/iPhone OS 3 (I'm assuming also Mac OS X and iOS 4) has the following behavior:
If you register yourself multiple times for the exact specific notification, NSNotificationCenter will NOT recognize the redundancy and instead will fire off as many notifications to you as you've registered an observation for.
This is almost never the behavior you want to see and is almost always accidental.
Example:
I want my view controller to receive notifications from a singleton network object when new data comes in:
- (void) viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(newDataArrived:)
name:NewDataArrivedNotification
object:[NetworkListener sharedNetworkListener]];
}
but earlier I'd already put the same thing in viewWillAppear:
- (void) viewWillAppear
{
[super viewWillAppear];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(newDataArrived:)
name:NewDataArrivedNotification
object:[NetworkListener sharedNetworkListener]];
}
Note that it's exactly the same notification, resolving to the same observer, sender and notification name.
In this case, if I don't remove one of those addObserver calls, I'll get duplicate notifications to my view controller.
In a multi-threaded environment, this is a world of hurt. Trust me.
Just putting this out there in case there are others who run into something like this.
You should and always clean up your observers.
The easiest way to do it is : [[NSNotificationCenter defaultCenter] removeObserver:self]
viewDidLoad is not a good place to add observers, because this functions may get called multiple times, this happens when viewDidUnload is triggered.
A good place to put your addObservers in viewWillAppear, and removeObservers in viewWillDisappear.
As you said yourself, NSNotificationCenter makes no check for duplicates, which may be annoying for some, but makes sense when concidering the full system behind it.
The same logic applies to adding targets to certain objects, but there is often a key recognition on those.
Thank you for the insight, and for a good, SEO-friendly warning :)