Can I Register a Class for NSNotifications? Can I use Class Methods with NSNotifications? - iphone

I'm working on a class for my iPhone app, and I'd like it to register for and be aware of application state changes (UIApplicationDidEnterBackgroundNotification, etc). Is there a way to register a class for notifications without having to keep an instantiated object in memory? I just want to have the appropriate notifications call the class to init, do some stuff, and then leave memory again.
Right now I have the following in the init method:
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(handleEnteredBackground)
name: UIApplicationDidEnterBackgroundNotification
object: nil];
and this method elsewhere in the .m file of the class:
- (void) handleEnteredBackground {
NSLog(#"Entered Background"); }
I instantiate the class once under applicationDidLoad, but since I don't do anything with it I presume ARC kills the object from memory and the app crashes (without any useful error codes, mind you) when I go to close it. If I switch handleEnteredBackground to a class method with a "+" sign, I get invalid selector errors when I close the app.
The end goal is to instantiate a class once in the lifecycle of an app and have it be able to respond to app state changes without any additional code outside the class. Assume iOS 5 + Xcode 4.2+

The following should work:
[[NSNotificationCenter defaultCenter] addObserver: [self class]
selector: #selector(handleEnteredBackground:)
name: UIApplicationDidEnterBackgroundNotification
object: nil];
The selector itself:
+ (void) handleEnteredBackground: (NSNotification *) notification
{
}
You don't have to unregister the observer, because the class object cannot be deallocated or otherwise destroyed. If you need to unregister the observer for other reasons, you can:
[[NSNotificationCenter defaultCenter] removeObserver: [self class]];

You should look into singletons.
You can easily create an object that lasts through the whole application lifecycle.
+ (id)sharedObserver
{
static dispatch_once_t once;
static YourObserverClass *sharedObserver = nil;
dispatch_once(&once, ^{
sharedObserver = [[self alloc] init];
});
return sharedObserver;
}
- (void)startObserving
{
// Add as observer here
}
Now you can call [[YourObserverClass sharedObserver] startObserving] and you don't have to worry about retaining it etc.

Related

Call Selector method from another Class - NSNotificationCenter

I would like to know how I can call the selector which is in another class when notification is posted.I am on tabbarcontroller.
The FirstViewController, SecondViewController are tab bar items
Inside `FirstViewController` I have the following
-(void)viewdidload
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(productPurchased:) name:kProductPurchasedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector: #selector(productPurchaseFailed:) name:kProductPurchaseFailedNotification object: nil];
}
- (void)productPurchased:(NSNotification *)notification {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
NSString *productIdentifier = (NSString *) notification.object;
NSLog(#"Purchased: %#", productIdentifier);
[appDelegate.myDownloadablePoemsArray addObject:productIdentifier];
[self.tabBarController setSelectedIndex:3];
}
- (void)productPurchaseFailed:(NSNotification *)notification {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
SKPaymentTransaction * transaction = (SKPaymentTransaction *) notification.object;
if (transaction.error.code != SKErrorPaymentCancelled) {
UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:#"Error!"
message:transaction.error.localizedDescription
delegate:nil
cancelButtonTitle:nil
otherButtonTitles:#"OK", nil] autorelease];
[alert show];
}
}
The above code is working fine. Now what the issue is, I want to call the same selector method from my another view say for example I have a view controller named SecondViewController, in that I am adding the same notification observer.
but the selector method is not called in the FirstViewController.
Inside SecondViewController I have the following
-(void)viewdidload
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(productPurchased:) name:kProductPurchasedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector: #selector(productPurchaseFailed:) name:kProductPurchaseFailedNotification object: nil];
}
But I want to call the selecor methods from FirstViewController;
Please let me know , is that possible ? And how can I do that?
Thanks a lot
in the SecondViewController change the self as observer to the pointer of the FirstViewController, because the instance of FirsViewController has the methods.
inside SecondViewController.m you must use these lines:
- (void)viewdidload {
[[NSNotificationCenter defaultCenter] addObserver:firstViewController selector:#selector(productPurchased:) name:kProductPurchasedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:firstViewController selector: #selector(productPurchaseFailed:) name:kProductPurchaseFailedNotification object: nil];
}
BUT! AND THIS IS THE POINT.
if the FirstViewController is already a valid and loaded view controller in the memory with the methods as you've mentioned above, and it is an observer already for these notifications in the NSNotificatioCenter, you don't need to add again it to the NSNotificationCenter because the FirstViewController can receive and will receive the desired notification still. (it is just not shown, because an other view controller covers it.)
if the FirstViewController is not exists yet when the SecondViewController is, you cannot reach any instance method called from an another class because the FirstViewController was not instantiated before, and you cannot add it to the NSNotificationCenter as well.
CONCLUSION
it would be better to isolate the purchase callbacks into a third class what you can use for every independent view controller, according to the spirit of the OOP and MVC.
If your view controllers are the roots of tab-bar controllers, once they are loaded the first time, they stay around unless manually replaced.
Thus, when you install the notification handler in the first controller, unless you remove the notification handler, it will still get them, even when the second controller is onscreen.
Now, it may get unloaded due to memory pressure, or by the custom tab-bar-controller code. However, it's highly unusual for a tab-bar controller to deallocate one of its view controllers, so your installed notification handlers will be around until you cancel them.
In fact, if both view controllers register for the notifications, then they will both get them.
You are registering in viewDidLoad so the first one will get registered immediately, as it will be loaded and displayed as the initial controller. It will continue to receive those notifications.
When the second one loads, it will also register. Now both view controllers are receiving the notifications. When you go back to the first view controller, they will both still be getting the notifications.

Sending a notification when the operation has been completed in one class?

I have two classes, Class A and Class B.
Class A has a method
-(void)methodA:(id)sender
{
}
and Class B has a method
-(void)methodB:(id)sender
{
}
Now i have some work is happening in methodA ,So once it is completed i want to send a notification from methodA: to methodB: So i can do some operation on the basis of notification.
So how can i do this? Can anybody guide me as i am new to obj-c?
Use delegate. Simple code from wiki: visit http://en.wikipedia.org/wiki/Delegation_pattern
A delegate is an object whose objects are (generally) called to handle or respond to specific events or actions.
You must "tell" an object which accepts a delegate that you want to be the delegate. This is done by calling [object setDelegate:self]; or setting object.delegate = self; in your code.
The object acting as the delegate should implement the specified delegate methods. The object often defines methods either in a protocol, or on NSObject via a category as default/empty methods, or both. (The formal protocol approach is probably cleaner, especially now that Objective-C 2.0 supports optional protocol methods.)
When a relevant event occurs, the calling object checks to see if the delegate implements the matching method (using -respondsToSelector:) and calls that method if it does. The delegate then has control to do whatever it must to respond before returning control to the caller.
Register an Observer in Class B like :
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(method:) name:#"notificationName" object:nil];
And Post the notification from Class A like:
[NSNotificationCenter defaultCenter] postNotificationName:#"notificationname" object:nil];
Remove the Observer in your class B, when it is deallocated like
[[NSNotificationCenter defaultCenter]removeObserver:self];
Easiest way.
In Class B's -(id)initWithNibName: Bundle: you will need to add in registering for NSNotifications.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(methodB:)
name:#"methodAFinished"
object:nil];
}
return self;
}
And then you need to do the following in the Class A methodA: function.
- (void)methodA:(id)sender {
// Once you have completed your actions do the following
[[NSNotificationCenter defaultCenter] postNotificationName:#"methodAFinished" object:nil];
}
- (void)methodB:(id)sender {
// This will then be called in the other class, do whatever is needed in here.
}
Hope that works for you!
Also, don't forget, in Class B's -(void)viewDidDisappear:animated function, you need to unregister for the notifications.
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
That should accomplish what you're asking for. Please add to your question if this isn't what you're working or comment below and I can rectify my answer.

NSNotification only NSLog works

In my Class X I post a Notification like this:
[[NSNotificationCenter defaultCenter] addObserver:viewController
selector:#selector(doThis:)
name:#"myNotification"
object:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:#"myNotification" object:nil];
In my Class Y I recieve it like this:
- (void) doThis: (NSNotification *) notification {
NSLog(#"It works.");
[uiTextView resignFirstResponder]; }
The console shows the NSLog message but my UITextView does not hide its keyboard.
(In e.g. viewDidLoad the resignFirstResponder/becomeFirstResponder works.)
Is there any special thing I need to do?
FWIW, in most, but not all, cases, observers should be added and removed by the observer itself, not by a separate object. (What happens if the observer goes away before the separate object, and fails to have the observer properly removed? Or vice-versa? It makes it all too easy to either leak observers or crash on notifications to deallocated objects.)
Anyhow, first thing's first: have you verified that uiTextView is not nil and points at the first responder? I rather suspect that uiTextView is not what you think it is.
As Conrad says, observers should be added and removed by themselves...
Use the best practice to define the name of the notifications as static constants like follows:
static NSString *const kMyNotification = #"myNotification";
Why? because there is a risk that both #"myNotification" might be two different objects and then the notificationName is different and you won't receive the notification. Since I always declare them as static constants I have never had issues with NSNotifications.
Then use it like this:
To register the observer
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(doThis:)
name: kMyNotification
object: nil];
To post the notification
[[NSNotificationCenter defaultCenter] postNotificationName: kMyNotification
object: nil];
To remove the observer:
[[NSNotificationCenter defaultCenter] removeObserver: self];

NSnotifications for multiple downloads

I have a parser class and some view controller classes. In the parser class i am sending a request and receiving an asynchronous response. I want multiple downloads, say one per viewcontroller. So i register an observer in each of these classes :
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(dataDownloadComplete:) name:OP_DataComplete object:nil];
and then post a notification in :
-(void)connectionDidFinishLoading:(NSURLConnection *)connection method of the parser class.
[[NSNotificationCenter defaultCenter] postNotificationName:OP_DataComplete object:nil];
but then on running the code the first viewcontroller works fine but for the second one onwards after download and parser class posting notification infinitely the code enters the first class's dataDownloadComplete: method although i have specified a different method name in the selector each time. I don't understand what the error might be. Please help. Thanks in advance.
Both view controllers are listening for the notification so both methods should be being called, one after another.
There's a few ways to solve this. The easiest would be for the notification to contain some sort of identifier that the view controller can look at to see if it should ignore it or not. NSNotifications have a userInfo property for this.
NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:#"viewController1", #"id", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:OP_DataComplete object:self userInfo:info];
and when you recieve the notification, check to see who it's for :
- (void)dataDownloadComplete:(NSNotification *)notification {
NSString *id = [[notification userInfo] objectForKey:#"id"];
if (NO == [id isEqualToString:#"viewController1"]) return;
// Deal with the notification here
...
}
There's a few other ways to deal with it but without knowing more about your code, I can't explain them well - basically you can specify the objects that you want to listen to notifications from (see how I have object:self but you sent object:nil) but sometimes your architecture won't allow that to happen.
it's better to create a protocol:
#protocol MONStuffParserRecipientProtocol
#required
- (void)parsedStuffIsReady:(NSDictionary *)parsedStuff;
#end
and to declare the view controller:
#class MONStuffDownloadAndParserOperation;
#interface MONViewController : UIViewController < MONStuffParserRecipientProtocol >
{
MONStuffDownloadAndParserOperation * operation; // add property
}
...
- (void)parsedStuffIsReady:(NSDictionary *)parsedStuff; // implement protocol
#end
and add some backend: to the view controller
- (void)displayDataAtURL:(NSURL *)url
{
MONStuffDownloadAndParserOperation * op = self.operation;
if (op) {
[op cancel];
}
[self putUpLoadingIndicator];
MONStuffDownloadAndParserOperation * next = [[MONStuffDownloadAndParserOperation alloc] initWithURL:url recipient:viewController];
self.operation = next;
[next release], next = 0;
}
and have the operation hold on to the view controller:
#interface MONStuffDownloadAndParserOperation : NSOperation
{
NSObject<MONStuffParserRecipientProtocol>* recipient; // << retained
}
- (id)initWithURL:(NSURL *)url Recipient:(NSObject<MONStuffParserRecipientProtocol>*)recipient;
#end
and have the operation message the recipient when the data is downloaded and parsed:
// you may want to message from the main thread, if there are ui updates
[recipient parsedStuffIsReady:parsedStuff];
there are a few more things to implement -- it's just a form. it's safer and involves direct messaging, ref counting, cancellation, and such.

iPhone - Launching selectors from a different class

I'd like to reload a table view which is in another class called "WriteIt_MobileAppDelegate" from one of my other classes which is called "Properties". I've tried to do this via the NSNotificationCenter class - the log gets called but the table is never updated.
Properties.h:
[[NSNotificationCenter defaultCenter] postNotificationName:#"NameChanged"
object:[WriteIt_MobileAppDelegate class]
userInfo:nil];
WriteIt_MobileAppDelegate.m
-(void)awakeFromNib {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(reloadItProperties:)
name:#"NameChanged" object:self];
}
- (void) reloadItProperties: (NSNotification *)notification {
NSLog(#"Reloading Data"); //this gets called
[[self navigationController] dismissModalViewControllerAnimated:YES];
[self.navigationController popToRootViewControllerAnimated:YES];
[self.tblSimpleTable reloadData];
[self.tblSimpleTable reloadSectionIndexTitles];
// but the rest doesn't
}
What am I doing wrong here?
Seems like you are using the object parameter wrong:
addObserver:selector:name:object:
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.