0

REVISED...

The crux of the app is communicating with a database server. Responses from the server to the app are all in XML. There are several screens. Example, screen 1 lists the user's information, screen 2 lists the user's past trades, allows new trades, and so on.

Here is some code from my AppDelegate:

StartViewController *svc = [[StartViewController alloc] init];
TradeViewController *tvc = [[TradeViewController alloc] init];
CashViewController *cvc = [[CashViewController alloc] init];
ComViewController *covc = [[ComViewController alloc] init];
PrefsViewController *pvc = [[PrefsViewController alloc] init];

NSMutableArray *tabBarViewControllers = [[NSMutableArray alloc] initWithCapacity:5];
UITabBarController *tabBarController = [[UITabBarController alloc] init];

UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:svc];
[tabBarViewControllers addObject:navigationController];
navigationController = nil;

navigationController = [[UINavigationController alloc] initWithRootViewController:tvc];
[tabBarViewControllers addObject:navigationController];
navigationController = nil;

navigationController = [[UINavigationController alloc] initWithRootViewController:cvc];
[tabBarViewControllers addObject:navigationController];
navigationController = nil;

navigationController = [[UINavigationController alloc] initWithRootViewController:covc];
[tabBarViewControllers addObject:navigationController];
navigationController = nil;

navigationController = [[UINavigationController alloc] initWithRootViewController:pvc];
[tabBarViewControllers addObject:navigationController];
navigationController = nil;

[tabBarController setViewControllers:tabBarViewControllers];

[[self window] setRootViewController:tabBarController];

self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];

Trying to stick with the MVC style, I have a singleton class which does all of the "processing".

Now an example on how I run into a wall… the user can change their email address on screen 5. Enter new email address into text field and click the save button. The button then calls a method from the singleton class which sends the new email address to the server and (via the URL) and receives a XML response confirming the change.

Here are my problems: 1. I start the spinner from the view controller before I make the singleton class method call - but not knowing when the app to server send/receive is finished, how do I make the spinner stop at the right time? I can't of it from the singleton class, I tried that. From what I know, it has to be from within the VC or is there a way to change VC output from my singleton class?

  1. The singleton class NSURLConnection is handling ALL of my communication. Everything from a simple, email change all the way to updating transaction tables. This just seems wrong to me and makes it very difficult to keep track on who is calling what. Again, I am going by my interpretation of MVC. I think it would be much easier to have a NSURLConnection for every VC and do some processing in those classes. However that would not be MVC(ish).

  2. I have close to a 100 variables, arrays, etc… in my singleton class which I use to assign values to all my VC. This also seems wrong to me but I can't think of any other way.

sangony
  • 11,636
  • 4
  • 39
  • 55
  • Please post how are you using same connection object with the several request. – Jitendra Singh Jun 04 '12 at 18:29
  • Jitendra - For simplicity sake lets say I have 3 UIButtons which, when touched, have 3 unique URLs to obtain data. Data which is used to update 3 tables respectively. Do you want me to post sample code? – sangony Jun 04 '12 at 18:44

5 Answers5

3

how can I distinguish in the NSURLConnection delegate (connectionDidFinishLoading) which URL call is being made?

Each of the delegate methods (such as -connectionDidFinishLoading:) has a connection parameter that tells you which connection sent the message. A given connection can only load one URL at a time, so there's a one to one correspondence between URLs and connections.

How can I tell outside of "connectionDidFinishLoading" when the download is completed?

That method tells you when the connection is finished. It's up to you to store that information somewhere where it's useful to your app.

Update: Based on what you've added, your "processing" class is your app's model. The rest of the app shouldn't care that each transaction involves a message to the server -- that's the model's business alone. Also, there's no reason that the model has to be a single object (let alone a singleton) -- it can be a group of objects that work together.

So, you might have a class (let's call it Processor) that represents the application's interface to the model (some might even call this a "model controller"). An instance of Processor might create a local database for storing the current local state of the app.You might also have a Transaction class that represents a single transaction with the server. A transaction could create a request, send it to the server, get the response, update the database, and tell the Processor that the transaction is done. Or, maybe when some other part of the app (like one of your view controllers) asks the Processor to process a new transaction, the Processor passes the requesting object along to the transaction that it creates so that the transaction can update the requestor directly.

It's hard to say what the best plan for your app is without knowing where you're planning on taking it, but the usual guidelines hold:

  • break your problem into parts that are easier to solve

  • limit the scope of each class's responsibilities

  • if something seems to complicated, it probably is

Breaking your model up into several classes will make it easier to test, as well. You can imagine how easy it would be to write a set of unit tests for the Transaction class. The same goes for Processor -- if the server transaction stuff is in a different class, it's easier to test that the Processor is doing the right thing.

Caleb
  • 124,013
  • 19
  • 183
  • 272
  • Caleb - I know the connectionDidFinishLoading is only executed when it has finished loading... My question relates to being able to know outside of it. For example, start a spinner upon making the call and knowing when it's done as to stop the spinner outside of the delegate. – sangony Jun 04 '12 at 18:47
  • 1
    @sangony Right, but the solution depends on your code. How are you keeping track of the various connections? What in your app depends on the state of the connections? Who's responsible for those things -- the connection delegate or some other object? Your connection delegate's implementation of the `didFinish` method could post a notification, or it could update an entry in a dictionary, or it could stop the spinner itself, or something else. The fact that each application will need to do something different is the reason that there's a `didFinish` delegate method in the first place. – Caleb Jun 04 '12 at 18:57
  • Right now I am trying to write generic code to get around that part but I am thinking about using a class variable (sorry if I am using the wrong term here) to keep track of who is making the call. Example, set classVar to 1 if button one is pressed to update data in table 1, set classVar to 2 if... In the didFinish I thought about using the classVar to see who is running. But being a newbie this looks somewhat wrong to me. I also thought about setting variables/dictionaries from didFinish... but how do I check for that on the outside? I thought about timers but that also seems wrong. – sangony Jun 04 '12 at 19:27
  • It might be easier to help if you expand your question to explain what you're *really* trying to accomplish. Why are you using one delegate for many connections? (Nothing wrong with that, but tell us what's going on.) How is the delegate related to the rest of the program? Like that. – Caleb Jun 04 '12 at 19:51
  • I posted more background. Hope this isn't overkill :) – sangony Jun 04 '12 at 21:00
  • Thanks a bunch for the answer. From what other people have answered, there are several ways to do what I want but none are rubber stamp easy. I am going to break up my "big" processor class into smaller pieces and work from there. All this would be A LOT easier if there were user groups in my area! – sangony Jun 05 '12 at 19:38
1

If you have multiple NSURLConnections for the same delegate, consider using a global (well, let's say rather an instance variable) NSMutableDictionary instance, in which you store the data depending on which NSURLConnection is being called. You can use, for example, the in-memory address of the connections converted to an NSString (something like

[NSString stringWithFormat:@"%p", connection]

should do the trick).

Also, in the connectionDidFinishLoading: and connection:didFailLoadWithError: methods, remove the keys corresponding to the NSURLConnections. Thus, you can tell it from 'outside' if a connection is finished: just check if it is in the dictionary or not.

1

If you're downloading any data over a network connection, I would suggest using ASIHttpRequest. This will allow you to download files asynchronously, meaning your interface doesn't freeze during the download process.

If you use ASIHttpRequest, you can also set the didFinishSelector. By doing this, you can control which method is called when a specific URL has finished loading.

Have a look at this:

NSURL *url = [NSURL URLWithString:@"http://allseeing-i.com"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];

[request setDelegate:self];
[request startAsynchronous];
[request setDidFinishSelector:@selector(requestDone:)];

Then:

- (void)requestDone:(ASIHTTPRequest *)request
{
   // Use when fetching text data
   NSString *responseString = [request responseString];

   // Use when fetching binary data
   NSData *responseData = [request responseData];

   // If you want, you can get the url of the request like this
   NSURL *url = [request url];
}

As for the second part of your question, if the requestDone: method has not been called, you know the download has not completed.

If you want to do something more complicated with multiple downloads, ASIHttpRequest offers queue functionality too. Take a look here.

Liam George Betsworth
  • 18,373
  • 5
  • 39
  • 42
  • `NSURLConnection` also operates asynchronously by default. That's why there are delegate methods; they are the callbacks to get the results of the connection. – jscs Jun 04 '12 at 18:48
  • I have zero exp. with ASIHttpRequest... does it handle https? – sangony Jun 04 '12 at 18:52
  • Yes, it does. Just use https for the URL, no issues at all. – Bill Burgess Jun 04 '12 at 18:53
  • 1
    This answer would probably work well for someone not very good at handling the multiple calls to the same delegate method. You could have a different return method for each callback (returnDone:). Granted it will make your app more code heavy, it will make it much easier to maintain and keep the callbacks straight. – Bill Burgess Jun 04 '12 at 18:54
  • 1
    Even the author of ASIHTTPRequest suggests using something else now that he's stopped further development on the project. – Caleb Jun 04 '12 at 19:04
  • Granted, ASIHttpRequest is no longer maintained but it still works. It's very popular also: http://allseeing-i.com/ASIHTTPRequest/Who-is-using-it – Liam George Betsworth Jun 05 '12 at 11:33
  • Thanks Liam. I will read up on ASIHTTPRequest and see what it can do for me. – sangony Jun 05 '12 at 14:45
1

I would recommend subclassing NSURLConnection. Simply add two properties: an NSInteger, tag, and a BOOL, isFinished. This way, you can #define tags for each different request and then identify them by tag in your delegate methods. In connectionDidFinishLoading, you can set the isFinished BOOL to YES, and then you can check in other methods if then connection is finished.


Here's my own NSURLConnection subclass, TTURLConnection:

TTURLConnection.h:

#import <Foundation/Foundation.h>

@interface TTURLConnection : NSURLConnection <NSURLConnectionDelegate>

@property (nonatomic) NSInteger tag;
@property (nonatomic) BOOL isLocked;

- (id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate startImmediately:
    (BOOL)startImmediately tag:(NSInteger)tagParam;

@end

TTURLConnection.m:

#import "TTURLConnection.h"

@implementation TTURLConnection

@synthesize tag;

- (id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate startImmediately:
    (BOOL)startImmediately tag:(NSInteger)tagParam {
    self = [super initWithRequest:request delegate:delegate 
        startImmediately:startImmediately];

    if(self) {
        self.tag = tagParam;
    }

    return self;
}

@end
eric.mitchell
  • 8,817
  • 12
  • 54
  • 92
1

Hope this will help you.

- (void)connectionDidFinishLoading:(NSURLConnection*)connection
{   
    NSString *urlString = [[[connection originalRequest] URL] absoluteString];
    if ([urlString caseInsensitiveCompare:@"http://www.apple.com"] == NSOrderedSame) {
        //Do Task#1
    }
    else if ([urlString caseInsensitiveCompare:@"http://www.google.com"] == NSOrderedSame)
    {
        //Do Task#2
    }
}
Jitendra Singh
  • 2,103
  • 3
  • 17
  • 26
  • Thanks for response Jitendra. Your solution is a more advanced and cleaner one of something I was considering. – sangony Jun 05 '12 at 19:30