Cocoa Programming for Mac OS X - Aaron Hillegass [115]
@private
NSMutableArray *classes;
NSMutableString *currentString;
NSMutableDictionary *currentFields;
NSDateFormatter *dateFormatter;
ScheduleFetcherResultBlock resultBlock;
NSMutableData *responseData;
NSURLConnection *connection;
}
- (void)fetchClassesWithBlock:(ScheduleFetcherResultBlock)theBlock;
@end
Now, in ScheduleFetcher.m, remove fetchClassesWithError: and implement fetchClassesWithBlock::
- (void)fetchClassesWithBlock:(ScheduleFetcherResultBlock)theBlock
{
// Copy the block to ensure that it is not kept on the stack:
resultBlock = [theBlock copy];
NSURL *xmlURL = [NSURL URLWithString:
@"http://bignerdranch.com/xml/schedule"];
NSURLRequest *req = [NSURLRequest requestWithURL:xmlURL
cachePolicy:NSURLRequestReturnCacheDataElseLoad
timeoutInterval:30];
connection = [[NSURLConnection alloc] initWithRequest:req
delegate:self];
if (connection)
{
responseData = [[NSMutableData alloc] init];
}
}
Note that theBlock is copied and the resulting pointer stored in resultBlock. We copy the block because it may still be on the stack of the calling method. If so, the block will be deallocated when that method exits. Because we are starting an asynchronous request and the calling method is guaranteed to return before the request completes, we need to be sure that the block’s memory will be valid until we call it with the results. If theBlock were going to be used only within this method and not after we return, copying it would not be necessary.
The resultBlock, connection, and responseData objects are created when the fetch is initiated. It’s a good idea to clean them up when the request completes. To reduce repetition, add a new method called cleanup:
- (void)cleanup
{
responseData = nil;
connection = nil;
resultBlock = nil;
}
Still in ScheduleFetcher.m, implement the NSURLConnection delegate methods:
#pragma mark -
#pragma mark NSURLConnection Delegate
- (void)connection:(NSURLConnection *)theConnection
didReceiveData:(NSData *)data
{
[responseData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)theConnection
{
[classes removeAllObjects];
NSXMLParser *parser = [[NSXMLParser alloc]
initWithData:responseData];
[parser setDelegate:self];
BOOL success = [parser parse];
if (!success)
{
resultBlock(nil, [parser parserError]);
}
else
{
NSArray *output = [classes copy];
resultBlock(output, nil);
}
[self cleanup];
}
- (void)connection:(NSURLConnection *)theConnection
didFailWithError:(NSError *)error
{
resultBlock(nil, error);
[self cleanup];
}
Response data is collected in connection:didReceiveData: and then parsed in connectionDidFinishLoading:. We then call resultBlock with the results or error condition.
Now it’s time to update RanchForecastAppDelegate to work with the new interface to ScheduleFetcher. In RanchForecastAppDelegate.m, update applicationDidFinishLaunching::
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[tableView setTarget:self];
[tableView setDoubleAction:@selector(openClass:)];
ScheduleFetcher *fetcher = [[ScheduleFetcher alloc] init];
[fetcher fetchClassesWithBlock:^(NSArray *theClasses,
NSError *error) {
if (theClasses)
{
classes = theClasses;
[tableView reloadData];
}
else
{
NSAlert *alert = [[NSAlert alloc] init];
[alert setAlertStyle:NSCriticalAlertStyle];
[alert setMessageText:@"Error loading schedule."];
[alert setInformativeText:[error localizedDescription]];
[alert addButtonWithTitle:@"OK"];
[alert beginSheetModalForWindow:self.window
modalDelegate:nil
didEndSelector:nil
contextInfo:nil];
}
}];
}
Build and run the application. Notice that the application appears more quickly now because the request is performed asynchronously.
Challenge: Design a Delegate
Earlier in this chapter, we discussed how we might use the delegate design pattern as a means for passing Web service data back to the interested parties. Create a copy of this project and refactor it to use this pattern instead of blocks. Remember