I was wondering what is the best practice for an application flow in iPhone development.
How do you pass messages between ViewControllers?
Do you use singletons? pass it between views or do you have a main controller for the application that manage the flow?
Thanks.
I use NSNotificationCenter, which is fantastic for this kind of work. Think of it as an easy way to broadcast messages.
Each ViewController you want to receive the message informs the default NSNotificationCenter that it wants to listen for your message, and when you send it, the delegate in every attached listener is run. For example,
ViewController.m
NSNotificationCenter *note = [NSNotificationCenter defaultCenter];
[note addObserver:self selector:#selector(eventDidFire:) name:#"ILikeTurtlesEvent" object:nil];
/* ... */
- (void) eventDidFire:(NSNotification *)note {
id obj = [note object];
NSLog(#"First one got %#", obj);
}
ViewControllerB.m
NSNotificationCenter *note = [NSNotificationCenter defaultCenter];
[note addObserver:self selector:#selector(awesomeSauce:) name:#"ILikeTurtlesEvent" object:nil];
[note postNotificationName:#"ILikeTurtlesEvent" object:#"StackOverflow"];
/* ... */
- (void) awesomeSauce:(NSNotification *)note {
id obj = [note object];
NSLog(#"Second one got %#", obj);
}
Would produce (in either order depending on which ViewController registers first):
First one got StackOverflow
Second one got StackOverflow
The NSNotification class is a bit heavyweight, but fits the usage you describe. The way it works is that your various NSViewControllers register with an NSNotificationCenter to receive the events they're interested in
Cocoa Touch handles the routing, including providing a singleton-like "default notification center" for you. See Apple's notification guide with more info.
Related
Is there a way onto the iPhone for an object to send a message without a specific receiver object, and into another object, listen to such messages, that could come with objects (parameters), and do what is needed ?
I searched around NSNotification but I don't see what I should do.
Objects that want to be notified need to register to receive notifications with the notification center. Thereafter, when a notification is posted to the notification center, the notification center will check it against all the registered filters, and the corresponding action will be taken for each matching filter.
A "filter" in this case is the pair of (notification name, notification object). A nil object in the filter is equivalent to any object (the notification object is ignored in matching). The name is required.
Example:
/* Subscribe to be sent -noteThis:
* whenever a notification named #"NotificationName" is posted to the center
* with any (or no) object. */
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(noteThis:)
name:#"NotificationName"
object:nil];
/* Post a notification. */
[nc postNotificationName:#"NotificationName" object:self userInfo:someDict];
/* Handle a notification. */
- (void)noteThis:(NSNotification *)note
{
id object = [note object];
NSDictionary *userInfo = [note userInfo];
/* take some action */
}
There is a more modern API using queues and blocks, but I find the old API easier to illustrate and explain.
Basically, you post a notification (NSNotification) to the shared class, NSNotificationCenter. Here's an example:
#define kNotificationCenter [NSNotificationCenter defaultCenter]
#define kNotificationToSend #"a notification name as a string"
//... Post the notification
[kDefaultCenter postNotificationNamed:knotificationToSend withObject:nil];
Any class that wants to listen, adds itself as an observer to the notifcation center. You must remove the observer as well.
[kNotificationCenter addObserver:self selector:#selector(methodToHandleNotification) object:nil];
//... Usually in the dealloc or willDisappear method:
[kNotificationCenter removeObserver:self];
You can do more with the notification center. See the NSNotificationCenter documentation fr complete reference.
I think NSNotification is the message object itself, to send to listen to what is sent try NSNotificationCenter. It has a singleton object, so to send the message:
NSNotification *notificationObj;
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center postNotification:notificationObj];
And the other class listen to with:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(method:) object:nil];
Make sure that class has method: method. You can have a single parameter, which is an NSNotification object that is sent earlier. The NSNotification object has [notificationObj object which you can get as a piece of data sent by the sender class. Alternatively, you might use [notificationObj userInfo] if you want it to be more structured.
you can initialise notificationObj and tailor it with the message that you'd want. More information on NSNotificationCenter, you can find it
http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSNotificationCenter_Class/Reference/Reference.html#//apple_ref/occ/cl/NSNotificationCenter
or for more information about NSNotification itself
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSNotification_Class/Reference/Reference.html
I make a call to a web service, passing a parameter and then register an observer in a viewcontroller class (to notify completion of download) :
[self callWebservice:parameter1];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(dataDownloadComplete:) name:OP_DataComplete object:nil];
and then post a notification in my parser class:
-(void)connectionDidFinishLoading:(NSURLConnection *)connection method of the parser class. [[NSNotificationCenter defaultCenter] postNotificationName:OP_DataComplete object:nil];
In the callback method dataDownloadComplete: i would like to call the same web service again several times.
-(void)dataDownloadComplete
{
if([anArray objectAtindex:N]<10)
{
[self callWebservice:parameterN];
NSLog(#"This is getting called everytime (9 times)");
[self writeintoDatabase];
N++;
}
}
But the problem is that i want to write into a database what data i download from the service. The DB writing happens for 'parameter1' call strangely, and goes on for the others but not for parameter9(which i need as well). Note the Log is called all 9 times though. The writeintoDatabase code is perfect. Please help. Thanks in advance.
My question is in regards the object that gets added to a -postNotificationName:object: userInfo: method.
Does the NSNotification retain the object ?
(in a similar fashion to NSMutableDictionary or Array) ... meaning I can release the object after posting the notification
Below is a code snippet to help describe my question ... is it valid to release the object. A link to Apple documentation could be really helpful.
NSMutableDictionary *teamDictCopy = [self.teamDict mutableCopy];
[teamDictCopy setObject:[NSNumber numberWithInt:self.scrollViewIndex] forKey:#"imageIndex"];
if([self.statusButton.title isEqualToString:#"Completed"]){
[[NSNotificationCenter defaultCenter] postNotificationName:#"UnComplete" object:teamDictCopy userInfo:nil];
}
[teamDictCopy release];
"Does the NSNotification retain the
object ? (in a similar fashion to
NSMutableDictionary or Array) ...
meaning I can release the object after
posting the notification"
I'm not sure if the object and userInfo parameters are retained by that method or not, but in practice, it shouldn't really matter.
I think you may be envisioning that NSNotificationCenter is creating these notifications and broadcasting them in an asynchronous manner, but that isn't the case. As stated in the documentation for NSNotificationCenter (see NSNotificationCenter Class Reference), notifications are posted synchronously:
A notification center delivers
notifications to observers
synchronously. In other words, the
postNotification: methods do not
return until all observers have
received and processed the
notification. To send notifications
asynchronously use
NSNotificationQueue. In a
multithreaded application,
notifications are always delivered in
the thread in which the notification
was posted, which may not be the same
thread in which an observer registered
itself.
So, in your code, the notification center creates the notification and then broadcasts it through the default center. Any objects which have registered for this combination of notification name and object will receive the notification and then perform the selector they specified when they registered for that notification. Afterwards, the control returns to the class that posted the notification.
In other words, by the time your code gets to the [teamDictCopy release] line, the teamDictCopy will already have been "used" by all of the interested parties. So, there shouldn't be any danger in releasing it.
Just a note on conventions. Generally, the object: parameter is meant to be the object that is posting the notification, and the userInfo: parameter is meant for an NSDictionary of extra information. So, normally, you would handle the notification like follows:
NSMutableDictionary *teamDictCopy = [self.teamDict mutableCopy];
[teamDictCopy setObject:
[NSNumber numberWithInt:self.scrollViewIndex] forKey:#"imageIndex"];
if([self.statusButton.title isEqualToString:#"Completed"]){
[[NSNotificationCenter defaultCenter] postNotificationName:#"UnComplete"
object:self userInfo:teamDictCopy];
}
[teamDictCopy release];
yes - you can release the object once it's been set as the notification's object.
you can also subclass.
as far as a specific document/statement: i don't remember one, specifically.
this is however the basis of objects, their instance variables, and distributed communications and signaling when types are identified as being an object.
i've written a test for you, so you can be assured of this. the use cases of notifications would be few if the object were not retained. just add a breakpoint where instructed, then run with breakpoints enabled. enjoy!
#import <Foundation/Foundation.h>
#interface MONObject : NSObject
#end
#implementation MONObject
- (id)retain {
return self; /* << add breakpoint here */
}
/* needed to counter retain override
(although all MONObjects will leak in this example)
*/
- (void)release {
}
#end
int main(int argc, const char* argv[]) {
NSAutoreleasePool * pool = [NSAutoreleasePool new];
NSString * name = #"UnComplete";
MONObject * obj = [MONObject new];
[[NSNotificationCenter defaultCenter] postNotificationName:name object:obj userInfo:nil];
[obj release], obj = 0;
[pool drain];
return 0;
}
So I have this function inside NotificationManager class.
-(void) postNotificationForClassName:(NSString*)className withObjects:(NSArray*)objects withError:(BOOL)withError
{
notificationName = [NSString stringWithFormat:#"didLoad%#",className];
[[NSNotificationCenter defaultCenter]
postNotificationName:notificationName object:self];
}
now, let's say I have 2 classes , A and B.
from A's method foo() I do the following:
[[NotificationManager sharedManager]
postNotificationForClassName:#"A" withObjects:objects withError:NO]
from B's method goo() I do the following:
[[NotificationManager sharedManager]
postNotificationForClassName:#"A" withObjects:objects withError:NO]
Now, I'm curious what should I do in case I want to listen only to notifications being posted from Class A.
Is this suppose to work ?
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(didLoadData:)
name:#"didLoadA" object:classAObject];
Cause I'm assuming that when I'm calling
[[NSNotificationCenter defaultCenter]
postNotificationName:notificationName object:self];
and I pass the "self", then the "self" will be the NotificationManager and not the class A or B that called the NotificationManager method.
Am I right or wrong here ? and if I'm right, is there a way to do what I want to accomplish?
Thanks!
self is a special variable, always referring to the object that received the message currently being handled. In NotificationManager's -postNotificationForClassName:withObjects:objectswithError:withError:, self is a NotificationManager (or descendent). As for self in the call to -addObserver:..., it depends on what method the call occurs in.
As you've noticed, -addObserver:... lets you monitor notifications from specific objects. You could use this to monitor messages from As if you use the class A as the object:
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(didLoadData:)
name:#"didLoadA" object:[A class]];
However, you'll also need to change the notification sender in -postNotificationForClassName:withObjects:objectswithError:withError:.
-(void)postNotificationForClassName:(NSString*)className
withObjects:(NSArray*)objects
withError:(BOOL)withError
from:(id)sender
{
notificationName = [NSString stringWithFormat:#"didLoad%#",className];
[[NSNotificationCenter defaultCenter]
postNotificationName:notificationName object:[sender class]];
}
I am trying to pass an NSDictionary form a UIView to a UIViewController using NSNotificationCenter. The dictionary works fine at the time the notification is posted, but in the receiving method I am unable to access any of the objects in the dictionary.
Here is how I am creating the dictionary and posting the notification...
itemDetails = [[NSDictionary alloc] initWithObjectsAndKeys:#"Topic 1", #"HelpTopic", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:#"HotSpotTouched" object:itemDetails];
In the UIViewController I am setting the observer...
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(hotSpotMore:)
name:#"HotSpotTouched"
object:nil];
For testing purposes hotSpotMore looks like this...
- (void)hotSpotMore:(NSDictionary *)itemDetails{
NSLog(#"%#", itemDetails);
NSLog(#"%#", [itemDetails objectForKey:#"HelpTopic"]);
}
The first NSLog works fine displaying the contents of the dictionary. The second log throws the following exception...
[NSConcreteNotification objectForKey:]: unrecognized selector sent to instance 0x712b130
I don't understand why I cannot access any objects in the passed dictionary.
Thanks in advance for any help.
John
The first NSLog works fine displaying
the contents of the dictionary. The
second log throws the following
exception...
The program tries to trick you, it just looks like it is your dictionary because your dictionary is inside the notification. From the exception you can see that your object actually is from a class named NSConcreteNotification.
This is because the argument of a notification-method is always a NSNotification-object.
this will work:
- (void)hotSpotMore:(NSNotification *)notification {
NSLog(#"%#", notification.object);
NSLog(#"%#", [notification.object objectForKey:#"HelpTopic"]);
}
just as a hint: the object is usually the object which sends the notification, you should send your NSDictionary as userInfo.
I think it would improve your code if you would do it like this:
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center postNotificationName:#"HotSpotTouched" object:self userInfo:itemDetails];
- (void)hotSpotMore:(NSNotification *)notification {
NSLog(#"%#", notification.userInfo);
NSLog(#"%#", [notification.userInfo objectForKey:#"HelpTopic"]);
}
The object parameter is used to distinguish between the different objects that can send a notification.
Let’s say you have two different HotSpot objects that can both send the notification. When you set the object in addObserver:selector:name:object: you can add a different observer for each of the objects. Using nil as the object parameter means that all notifications should be received, regardless of the object that did send the notification.
E.g:
FancyHotSpot *hotSpotA;
FancyHotSpot *hotSpotB;
// notifications from hotSpotA should call hotSpotATouched:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(hotSpotATouched:) name:#"HotSpotTouched"
object:hotSpotA]; // only notifications from hotSpotA will be received
// notifications from hotSpotB should call hotSpotBTouched:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(hotSpotBTouched:) name:#"HotSpotTouched"
object:hotSpotB]; // only notifications from hotSpotB will be received
// notifications from all objects should call anyHotSpotTouched:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(anyHotSpotTouched:) name:#"HotSpotTouched"
object:nil]; // nil == “any object”, so all notifications with the name “HotSpotTouched” will be received
- (void)hotSpotATouched:(NSNotification *)n {
// only gets notification of hot spot A
}
- (void)hotSpotBTouched:(NSNotification *)n {
// only gets notification of hot spot B
}
- (void)anyHotSpotTouched:(NSNotification *)n {
// catches all notifications
}
This is the best way to pass your dictionary data with NSNotification.
Post Notification :
[[NSNotificationCenter defaultCenter] postNotificationName:#"Put Your Notification Name" object:self userInfo:"Pass your dictionary name"];
Add Observer for Handle the Notification.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(mydictionaryData:) name:#"Put Your Notification Name" object:nil];
Put Notification Handler method.
- (void)mydictionaryData::(NSNotification*)notification{
NSDictionary* userInfo = notification.userInfo;
NSLog (#"Successfully received test notification! %#", userInfo);}
I hope, this solution will help you
The method Matthias is talking about and the one I think you should be using is
postNotificationName:object:userInfo:
Where object is the sender and userInfo is your dictionary.