NSnotifications for multiple downloads - iphone

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.

Related

Passing Delegate through multiple hierarchy

I wrote a custom class, TagsScrollView, which displays tags inside a scroll view.
When the tags are pressed, TagsScrollView relies on its delegate to implement what to do. Almost all the time, this involves:
Changing the tab index to another index
Pushing TagsDetailVC to the current navigation controller.
Right now, this is how my app is structured:
Dotted line indicates a "has" relationship. MainVC has a FeedView, which has a few FeedCellViews, which in turn has a TagsScrollView each.
Solid line indicates a "push" relationship. ImageDetailVc is pushed onto the navController of MainVC.
How can I organize my code such that TagsScrollView's delegate can be pointed at MainVC elegantly?
right now I have defined the following:
TagsScrollView.h
#protocol TagPressedDelegate<NSObject>
#required
- (void)tagPressed:(id)sender forQuery:(NSString *)query;
#end
FeedCellView.m
self.tagsScrollView.tagPressedDelegate = self.tagPressedDelegate
FeedView.m
self.cells[0].tagPressedDelegate = self.tagPressedDelegate
MainViewVC.m
self.feed.tagPressedDelegate = self
....
- (void)tagPressed...
How can I avoid this pattern? What can I do better? Should I have TagsScrollViewDelegate extend ScrollViewDelegate instead?
You can definitely do better, remove the delegation pattern, use blocks.
Add a block based property to your TagsScrollView .h file
#property (copy, nonatomic) void (^tagPressedBlock)(id sender, NSString *query);
in the .m file add the related callbacks
- (void)tagPressed:(id)sender {
if (_tagPressedBlock) {
_tagPressedBlock(sender, self.query); // I'm assuming that the query is your iVar
}
}
assign the property like this
tagsScrollView.tagPressedBlock = ^(id sender, NSString *query) {
// do stuff with those parameters
}
That's for "doing it better"
As for how to pass a tag pressed event to your MainVC class, you should use NSNotificationCenter.
Define the notification name somewhere globally visible, for instance I'd suggest creating a Defines.h file and #including it in your Prefix.pch file.
Anyway, define the notification name:
static NSString *const TagPressedNotification = #"TagPressedNotification";
Next publish that notification when the -tagPressed: is executed and encapsulate the valuable information into the userInfo dictionary:
- (void)tagPressed:(id)sender {
[[NSNotificationCenter defaultCenter] postNotificationName:TagPressedNotification object:nil userInfo:#{#"sender" : sender, #"query" : self.query, #"scrollView" : self.tagScrollView}];
//.. code
}
Next add your MainVC as an observer to that notification:
MainVC.m
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(tagPressed:)
name:TagPressedNotification
object:nil];
}
And implement -tagPressed: method in your MainVC
- (void)tagPressed:(NSNotification *)notification {
id sender = notification.userInfo[#"sender"];
NSString *query = notification.userInfo[#"query"];
TagScrollView *scrollView = notification.userInfo[#"scrollView"];
if (scrollView == myScrollView) { // the one on your mainVC
// do stuff
}
}
Add don't forget to clean yourself out of NotificationCenter's register:
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
easy
edit
I suppose you should also pass the scroll view, which is the sender, since your mainVC contains that scroll view as well. Edited the code
Another edit
Create an enumeration define in your Defines.h file
enum {
TagSenderTypeFeed = 1,
TagSenderTypeImageDetail
};
typedef NSInteger TagSenderType;
When creating a notification add appropriate enum value to your notification's userInfo dictionary #"senderType" : #(TagSenderTypeFeed)

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.

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

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.

Passing Objects Between Classes

In my AppDelegate, I download some data from the internet and store it into an array. I want one of my ViewControllers to access that array. How would I go about in doing so? Is this a good situation to implement a delegate or a protocol? If so, can someone recommend a good tutorial for that?
Thank you
EDIT:
Please note that the data refreshes upon each launch so there is no need for Core Data or plists. Furthermore, the data are custom objects which I created so they can't be stored in a plist for example.
You have 2 options:
Implement a delegate protocol
Use NSNotifications
The advantages/disadvantages of each is set out well in this question and answer:
Delegates v Notifications
As notifications are easier to implement and may well be sufficient for your needs, you can implement it with the following steps:
In the class where you download the data:
When the data has been downloaded and the array populated, include the following lines:
NSDictionary *dict = [NSDictionary dictionaryWithObject:array forKey:#"Data"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"DataDownloaded" object:self userInfo:dict];
In the class where you want to receive the data:
2.1 Add the following line to your viewDidLoad method:
`[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(dataDownloaded:) name:#"DataDownloaded" object:nil];
2.2 Create the dataDownloaded selector:
(void)dataDownloaded:(NSNotification *)note {
NSDictionary *dict = note.userInfo;
NSArray *dataArray = [note.userInfo objectForKey:#"DataDownloaded"];
2.3 Add the following line to dealloc and viewDidUnload:
[[[NSNotificationCenter defaultCenter] removeObserver:self];
You can store data in plist file and use it in all view controllers. This way, you need not worry about the size of data (as you will load it on demand and free it immediately).
if you want to store your array in delegate then in any view you have to create reference of your delegate and you can access your array in that view like :
in your other view you have to write : in .h file
#import "YourDelegateFile.h" and declare a varialble
YourDelegateFile *appDelegate ;
in your .m file :
- (void) viewDidLoad
{
appDelegate = (YourDelegateFile *)[UIApplication sharedApplication] delegate];
NSArray *aArray = [appDelegate yourArrayName]; //yourArrayName is an array that you have declare in delegate
}
hope it helps you.
you just need to access the data stored within the appdelegate. I dont think this is the best solution to your problem but in order to do things the way you want.
so declare you property in your Appdelegate .h file
NSMutableArray* myArray_;
then add a property to the same file
#property (nonatomic, retain) NSMutableArray* myArray;
in the .m file
make sure you synthesize your property
#synthesize myArray = myArray_;
somewhere in your appdelegate .m file you will set the value
then, elsewhere in your code you can access the property in the appdelegate like so
MyAppDelegate *appDelegate = (MyAppDelegate *)[UIApplication sharedApplication].delegate
NSMutableArray* localArray = appDelegate.myArray;
Note, for good encapsulation you should really use an NSArray but i used mutable to keep the code short.
Also, using the appdelegate as a global store for program data is not a good idea, it breaks a lot of rules you shouldnt break, single responsibility principle being a good one to start with. You should ideally be storing application data in a dedicated class, perhaps a singleton or for much better testability a single instance class served by a factory class. This way you data is accessible from a known well defined entity, it is testable and it honours good design principles
You can send notification if app delegate got new data and all interested controllers will know that they need to update views. For this you can use NSNotificationCenter. For example
- (void)newDataLoaded {
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:arrayOfData forKey:#"data"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"data updated notification name" object:nil userInfo:userInfo];
}
If some controller interested in data updates it should subscribe for this notification as soon as possible:
- (void)viewDidLoad {
...
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(dataUpdatedNotificationHandler:) name:#"data updated notification name" object:nil];
...
}
Do not forget to unsubscribe from notifications if you don't need it. For this use [[NSNotificationCenter defautCenter] removeObserver:self] in viewDidUnload and dealloc methods.
- (void)dataUpdatedNotificationHandler:(NSNotification*)notification {
NSArray *data = [[notification userInfo] objectForKey:#"data"];
// update your view here
}

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.