NSURLConnection delegate - objective-c

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?
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).
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.

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.

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.

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.

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
}
}

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

Related

Have buttons override other processes

In my app, the root view controller acquires information from the internet, parses it, and displays it in viewDidAppear. I am using this method because my app in embedded in a UINavigationController and this way the root view controller will reload its data when the user presses the back button and pops to the root view.
When this occurs, it takes some time for the information from the internet to be acquired and displayed. During this time, if the user clicks a button to move to a different view, the button action will not occur until the view controller has completed its process of acquiring the web data.
How can I make it so that the buttons will override the other processes and immediately switch the view? Is this safe? Thanks in advance.
Edit
Here's an example of a portion where I take the information off of the site (My app parses the HTML).
NSURL *siteURL = [NSURL URLWithString:#"http://www.ridgefield.org/ajax/dist/emergency-announcements"];
NSError *error;
NSString *source = [NSString stringWithContentsOfURL:siteURL
encoding:NSUTF8StringEncoding
error:&error];
This is where Apple folks would hound, "don't block the main thread!".
The primary suggestion for this kind of workflow is to use a separate thread (read: queue) for loading data from the web. Then the worker who completes the load can set some property on your view controller, and inside that setter is where the UI should be updated. Remember to call the setter back on the main thread.
There are several ways to skin the concurrency cat, but the answer to this particular question leaves them out of scope. The short answer is to not do the load in the main thread, and that should lead you to the right place.
The NSString method stringWithContentsOfURL is synchronous and will block your main thread.
Instead of using background threads to solve the problem, you can use asynchronous URL requests. This does not block the user interface because a delegate protocol is used to let you know when the request is complete. For example:
NSURL *url = [NSURL URLWithString:#""http://www.ridgefield.org/ajax/dist/emergency-announcements""];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
NSURLConnection* theConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
And then some of your delegate methods are:
-(void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)theResponse
{
// create received data array
_receivedData = [[NSMutableData alloc] init];
}
-(void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)theData
{
// append to received data.
[_receivedData appendData:theData];
}
-(void)connectionDidFinishLoading:(NSURLConnection*)connection
{
// now the connection is complete
NSString* strResult = [[NSString alloc] initWithData: _receivedData encoding:NSUTF8StringEncoding];
// now parse strResult
}

Delegate property gets unset?

I'm running into a problem that I'm not sure how to solve. Let me just give some relevant code.
FrontpageViewController (viewDidLoad)
NewsFetcher *newsFetcher = [[NewsFetcher alloc] initWithURL:url];
newsFetcher.delegate = self;
[newsFetcher loadData];
NewsFetcher.h
#property (nonatomic, unsafe_unretained) id <NewsFetcherDelegate> delegate;
I'm using unsafe_unretained because I want my app to work with iOS 4 as well, while still using ARC for convenience.
NewsFetcher.m
- (id)initWithURL:(NSURL *)url {
self = [super init];
if (self) {
self.url = url;
self.receivedData = [[NSData alloc] init];
}
return self;
}
- (void)loadData {
NSLog(#"%#", self.delegate); // FrontpageViewController, as expected
NSURLRequest *request = [NSURLRequest requestWithURL:self.url
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:15];
if (self.connectionInProgress)
[self.connectionInProgress cancel];
self.connectionInProgress = [[NSURLConnection alloc] initWithRequest:request
delegate:self
startImmediately:YES];
}
This all works fine. NewsFetcher conforms to the NSURLConnectionDelegate protocol, so the next method that's being called is connection:didReceiveData:. However, when I do another NSLog(#"%#", self.delegate) within that method, I get varied results (EXEC_BAD_ACCESS, NSCFDictionary, etc.). I think this means that my delegate property points to a released object, which is weird because it's supposed to be the view controller that's still on screen (and therefore couldn't have been released, right?).
How is my delegate available in one method, but not anymore in the next method? Does it have to do with the unsafe_unretained?
Delegate objects are not retained (by convention) by callers. The expectation is that the caller who set it on your object will retain it. Recommend you use the Instruments tool with zombies (and then with leaks) to see what's going on.

Objective-C – Delegate is dealloced?

I have a view controller that lists some data in an UITableView. To get the data downloaded I use ASIHTTPRequest which methods I have put in another class.
In my view controller I have setup the appropriate delegates to handle the data that is being retrieved from ASIHTTPRequest. So from my view controller in - viewDidLoad I alloc and init my class that holds the ASIHTTPRequest methods like so:
self.officesParser = [[[OfficesParser alloc] init] autorelease]; // retained property
Then in - viewDidAppear: I call [officesParser downloadOffices];
My - downloadOffices method looks like this:
- (void)downloadOffices {
// 1. Downloaded offices.json
NSURL *officesUrl = [NSURL URLWithString:#"http://example.com/example.json"];
ASIHTTPRequest *officesRequest = [ASIHTTPRequest requestWithURL:officesUrl];
// Always ask the server if there is new content available,
// If the request fails, use data from the cache even if it should have expired.
[officesRequest setCachePolicy:ASIAskServerIfModifiedCachePolicy|ASIFallbackToCacheIfLoadFailsCachePolicy];
// Store the cache permanently
[officesRequest setCacheStoragePolicy:ASICachePermanentlyCacheStoragePolicy];
[officesRequest setTag:1];
OfficesViewController *vc = [[OfficesViewController alloc] init];
[officesRequest setDelegate:vc];
[vc release];
[officesRequest startAsynchronous];
}
Everytime after calling the [officesParser downloadOffices] method I get:
*** -[OfficesViewController respondsToSelector:]: message sent to deallocated instance 0x6a2f6c0
What am I doing wrong here?
You want vc to be delegate for officesRequest, however, after you allocate and initialize vc and set it to be the delegate, you immediately release it. Please note that delegate properties are usually assign, not retain. You are then responsible for keeping your delegate object in existence until no longer needed. So, if you plan to send messages to it in a near future, you can't immediately release it.

Navigating between View controllers?

In my Iphone application I am trying to navigate from one table view controller to next table view controller. Problem I am facing is that I have to fetch data using http request and then parse this data when the user select a cell. I am able to fetch and parse the data but the view controller is not waiting for the data to parsed and the next view controller is shown (which is empty). How to over come this problem.
indexSelected = [NSString stringWithFormat: #"%d",[indexPath row] ];
[[MySingletonClass sharedMySingleton] doAnAuthenticatedAPIFetch_Subscriber_Detail : indexSelected];
SubscribersDetailViews2 *viewController = [[SubscribersDetailViews2 alloc] initWithNibName:#"SubscribersDetailViews2" bundle:nil];
[[self navigationController] pushViewController:viewController animated:YES];
[viewController release];
This is what you do:
indexSelected = [NSString stringWithFormat: #"%d",[indexPath row] ];
SubscribersDetailViews2 *viewController = [[SubscribersDetailViews2 alloc] initWithNibName:#"SubscribersDetailViews2" bundle:nil];
[[MySingletonClass sharedMySingleton] doAnAuthenticatedAPIFetch_Subscriber_Detail:indexSelected delegate:self];
[[self navigationController] pushViewController:viewController animated:YES];
[viewController release];
You define a protocol that your view controller conforms to and when the fetching and parsing of data is done you call a method on the delegate to let the view controller know that the data is ready to be displayed.
If you need more information on how to do this, leave a comment.
EDIT: So here's how to declare and use a protocol. I'm going to try to keep it as simple as possible. I'm not sure if I like your naming convention, but I'll still use it for this example.
So let's get down to the code. This is how you declare a protocol:
#protocol MySingletonClassDelegate <NSObject>
#optional
- (void)didDoAnAuthenticatedAPIFetch_Subscriber_Detail_WithData:(NSArray *)data;
- (void)failedToDoAnAuthenticatedAPIFetch_Subscriber_Detail_WithError:(NSError *)error;
#end
Again, I'm not too fond of the naming convention. You shouldn't have underscores in objective-c method names.
The protocol should be defined in MySingletonClass.h before the declaration of MySingletonClass.
I declared two methods in the protocol, one for delivering the data and one for delivering an error if it fails, so that you can notify the user that it failed.
To use the protocol you need the following:
#interface SubscribersDetailViews2 : UITableViewController <MySingletonClassDelegate>
You also need to implement the methods declared in the protocol, but I'll leave that implementation to you.
Since the fetching of data already seems to be happening in the background I don't think I'll need to explain how to do that. One important thing to remember is that you want to execute the delegate methods on the main thread. Here's the code to do that:
- (void)doAnAuthenticatedAPIFetch_Subscriber_Detail:(NSUInteger)index delegate:id<MySingletonClassDelegate>delegate {
// Fetching data in background
if (successful) {
[self performSelectorOnMainThread:#selector(didDoAnAuthenticatedAPIFetch_Subscriber_Detail_WithData:) withObject:data waitUntilDone:NO];
} else {
[self performSelectorOnMainThread:#selector(failedToDoAnAuthenticatedAPIFetch_Subscriber_Detail_WithError:) withObject:error waitUntilDone:NO];
}
}
Just to be clear the // Fetching data in background is supposed to be replaced by your code. I assume that your code produces the variables (NSArray *data, NSError *error, BOOL successful) that I use.
That's about it, if you need clarification on anything let me know.
There are a number of options:
Cache the data, i.e., take a full copy of it on the iOS device (may not be practical of course)
Display an interstitial screen saying "loading" and then move to the "real" screen when the data has downloaded
Have, effectively, two different data sources for your table. The first is your current one. The second would be a single cell saying "Loading..."
In short, there's no point and click way of doing this but there's no problem downloading the data on the fly as long as you tell your users what's happening.

Populating NSImage with data from an asynchronous NSURLConnection

I have hit the proverbial wall trying to figure out how to populate an NSImage with data returned from an asynchronous NSURLConnection in my desktop app (NOT an iPhone application!!).
Here is the situation.
I have a table that is using custom cells. In each custom cell is an NSImage which is being pulled from a web server. In order to populate the image I can do a synchronous request easily:
myThumbnail = [[NSImage alloc] initWithContentsOfFile:myFilePath];
The problem with this is that the table blocks until the images are populated (obviously because it's a synchronous request). On a big table this makes scrolling unbearable, but even just populating the images on the first run can be tedious if they are of any significant size.
So I create an asynchronous request class that will retrieve the data in its own thread as per Apple's documentation. No problem there. I can see the data being pulled and populated (via my log files).
The problem I have is once I have the data, I need a callback into my calling class (the custom table view).
I was under the impression that I could do something like this, but it doesn't work because (I'm assuming) that what my calling class really needs is a delegate:
NSImage * myIMage;
myImage = [myConnectionClass getMyImageMethod];
In my connection class delegate I can see I get the data, I just don't see how to pass it back to the calling class. My connectionDidFinishLoading method is straight from the Apple docs:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// do something with the data
// receivedData is declared as a method instance elsewhere
NSLog(#"Succeeded! Received %d bytes of data",[receivedData length]);
// release the connection, and the data object
[connection release];
[receivedData release];
}
I am hoping this is a simple problem to solve, but I fear I am at the limit of my knowledge on this one and despite some serious Google searches and trying many different recommended approaches I am struggling to come up with a solution.
Eventually I will have a sophisticated caching mechanism for my app in which the table view checks the local machine for the images before going out and getting them form the server and maybe has a progress indicator until the images are retrieved. Right now even local image population can be sluggish if the image's are large enough using a synchronous process.
Any and all help would be very much appreciated.
Solution Update
In case anyone else needs a similar solution thanks to Ben's help here is what I came up with (generically modified for posting of course). Bear in mind that I have also implemented a custom caching of images and have made my image loading class generic enough to be used by various places in my app for calling images.
In my calling method, which in my case was a custom cell within a table...
ImageLoaderClass * myLoader = [[[ImageLoaderClass alloc] init] autorelease];
[myLoader fetchImageWithURL:#"/my/thumbnail/path/with/filename.png"
forMethod:#"myUniqueRef"
withId:1234
saveToCache:YES
cachePath:#"/path/to/my/custom/cache"];
This creates an instance of myLoader class and passes it 4 parameters. The URL of the image I want to get, a unique reference that I use to determine which class made the call when setting up the notification observers, the ID of the image, whether I want to save the image to cache or not and the path to the cache.
My ImageLoaderClass defines the method called above where I set what is passed from the calling cell:
-(void)fetchImageWithURL:(NSString *)imageURL
forMethod:(NSString *)methodPassed
withId:(int)imageIdPassed
saveToCache:(BOOL)shouldISaveThis
cachePath:(NSString *)cachePathToUse
{
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:imageURL]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// Create the connection with the request and start loading the data
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection) {
// Create the NSMutableData that will hold
// the received data
// receivedData is declared as a method instance elsewhere
receivedData = [[NSMutableData data] retain];
// Now set the variables from the calling class
[self setCallingMethod:methodPassed];
[self setImageId:imageIdPassed];
[self setSaveImage:shouldISaveThis];
[self setImageCachePath:cachePathToUse];
} else {
// Do something to tell the user the image could not be downloaded
}
}
In the connectionDidFinishLoading method I saved the file to cache if needed and made a notification call to any listening observers:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(#"Succeeded! Received %d bytes of data",[receivedData length]);
// Create an image representation to use if not saving to cache
// And create a dictionary to send with the notification
NSImage * mImage = [[NSImage alloc ] initWithData:receivedData];
NSMutableDictionary * mDict = [[NSMutableDictionary alloc] init];
// Add the ID into the dictionary so we can reference it if needed
[mDict setObject:[NSNumber numberWithInteger:imageId] forKey:#"imageId"];
if (saveImage)
{
// We just need to add the image to the dictionary and return it
// because we aren't saving it to the custom cache
// Put the mutable data into NSData so we can write it out
NSData * dataToSave = [[NSData alloc] initWithData:receivedData];
if (![dataToSave writeToFile:imageCachePath atomically:NO])
NSLog(#"An error occured writing out the file");
}
else
{
// Save the image to the custom cache
[mDict setObject:mImage forKey:#"image"];
}
// Now send the notification with the dictionary
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc postNotificationName:callingMethod object:self userInfo:mDict];
// And do some memory management cleanup
[mImage release];
[mDict release];
[connection release];
[receivedData release];
}
Finally in the table controller set up an observer to listen for the notification and send it off to the method to handle re-displaying the custom cell:
-(id)init
{
[super init];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(updateCellData:) name:#"myUniqueRef" object:nil];
return self;
}
Problem solved!
My solution is to use Grand Central Dispatch (GCD) for this purpose, you could save the image to disc too in the line after you got it from the server.
- (NSView *)tableView:(NSTableView *)_tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
SomeItem *item = [self.items objectAtIndex:row];
NSTableCellView *cell = [_tableView makeViewWithIdentifier:tableColumn.identifier owner:self];
if (item.artworkUrl)
{
cell.imageView.image = nil;
dispatch_async(dispatch_queue_create("getAsynchronIconsGDQueue", NULL),
^{
NSURL *url = [NSURL URLWithString:item.artworkUrl];
NSImage *image = [[NSImage alloc] initWithContentsOfURL:url];
cell.imageView.image = image;
});
}
else
{
cell.imageView.image = nil;
}
return cell;
}
(I am using Automatic Reference Counting (ARC) therefore there are no retain and release.)
Your intuition is correct; you want to have a callback from the object which is the NSURLConnection’s delegate to the controller which manages the table view, which would update your data source and then call -setNeedsDisplayInRect: with the rect of the row to which the image corresponds.
Have you tried using the initWithContentsOfURL: method?