- (void)applicationDidFinishLaunching:(UIApplication *)application {
// Add the navigation view controller to the window.
[window addSubview:navigationController.view];
self.rootViewController.managedObjectContext = self.managedObjectContext;
// Use NSURLConnection to asynchronously download the data. This means the main thread will not
// be blocked - the application will remain responsive to the user.
//
// IMPORTANT! The main thread of the application should never be blocked!
// Also, avoid synchronous network access on any thread.
//
static NSString *feedURLString = @"http://earthquake.usgs.gov/eqcenter/catalogs/7day-M2.5.xml";
NSURLRequest *earthquakeURLRequest =
[NSURLRequest requestWithURL:[NSURL URLWithString:feedURLString]];
self.earthquakeFeedConnection =
[[[NSURLConnection alloc] initWithRequest:earthquakeURLRequest delegate:self] autorelease];
// Test the validity of the connection object. The most likely reason for the connection object
// to be nil is a malformed URL, which is a programmatic error easily detected during development.
// If the URL is more dynamic, then you should implement a more flexible validation technique,
// and be able to both recover from errors and communicate problems to the user in an
// unobtrusive manner.
NSAssert(self.earthquakeFeedConnection != nil, @"Failure to create URL connection.");
// Start the status bar network activity indicator. We'll turn it off when the connection
// finishes or experiences an error.
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
parseQueue = [NSOperationQueue new];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(addEarthquakes:)
name:kAddEarthquakesNotif
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(earthquakesError:)
name:kEarthquakesErrorNotif
object:nil];
}
#pragma mark -
#pragma mark NSURLConnection delegate methods
// The following are delegate methods for NSURLConnection. Similar to callback functions, this is
// how the connection object, which is working in the background, can asynchronously communicate back
// to its delegate on the thread from which it was started - in this case, the main thread.
//
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
// check for HTTP status code for proxy authentication failures
// anything in the 200 to 299 range is considered successful,
// also make sure the MIMEType is correct:
//
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if ((([httpResponse statusCode]/100) == 2) && [[response MIMEType] isEqual:@"application/atom+xml"]) {
self.earthquakeData = [NSMutableData data];
} else {
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:
NSLocalizedString(@"HTTP Error",
@"Error message displayed when receving a connection error.")
forKey:NSLocalizedDescriptionKey];
NSError *error = [NSError errorWithDomain:@"HTTP" code:[httpResponse statusCode] userInfo:userInfo];
[self handleError:error];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[earthquakeData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
if ([error code] == kCFURLErrorNotConnectedToInternet) {
// if we can identify the error, we can present a more precise message to the user.
NSDictionary *userInfo =
[NSDictionary dictionaryWithObject:
NSLocalizedString(@"No Connection Error",
@"Error message displayed when not connected to the Internet.")
forKey:NSLocalizedDescriptionKey];
NSError *noConnectionError = [NSError errorWithDomain:NSCocoaErrorDomain
code:kCFURLErrorNotConnectedToInternet
userInfo:userInfo];
[self handleError:noConnectionError];
} else {
// otherwise handle the error generically
[self handleError:error];
}
self.earthquakeFeedConnection = nil;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
self.earthquakeFeedConnection = nil;
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
// Spawn an NSOperation to parse the earthquake data so that the UI is not blocked while the
// application parses the XML data.
//
// IMPORTANT! - Don't access or affect UIKit objects on secondary threads.
//
ParseOperation *parseOperation = [[ParseOperation alloc] initWithData:self.earthquakeData];
[self.parseQueue addOperation:parseOperation];
[parseOperation release]; // once added to the NSOperationQueue it's retained, we don't need it anymore
// have our rootViewController observe the ParseOperation's save operation with its managed object context
[[NSNotificationCenter defaultCenter] addObserver:self.rootViewController
selector:@selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:parseOperation.managedObjectContext];
// earthquakeData will be retained by the NSOperation until it has finished executing,
// so we no longer need a reference to it in the main thread.
self.earthquakeData = nil;
}
// Handle errors in the download by showing an alert to the user. This is a very
// simple way of handling the error, partly because this application does not have any offline
// functionality for the user. Most real applications should handle the error in a less obtrusive
// way and provide offline functionality to the user.
//
- (void)handleError:(NSError *)error {
NSString *errorMessage = [error localizedDescription];
UIAlertView *alertView =
[[UIAlertView alloc] initWithTitle:
NSLocalizedString(@"Error Title",
@"Title for alert displayed when download or parse error occurs.")
message:errorMessage
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alertView show];
[alertView release];
}
// Our NSNotification callback from the running NSOperation when a parsing error has occurred
//
- (void)earthquakesError:(NSNotification *)notif {
assert([NSThread isMainThread]);
[self handleError:[[notif userInfo] valueForKey:kEarthquakesMsgErrorKey]];
}