Timers are simple. They only do one thing: fire. Thus, target-action is a good fit. A lot of simple user interface controls, like buttons and sliders, also use the target-action mechanism. What about something more complex?
In Chapter 23, you used an NSURLConnection method to fetch data from a web server. It worked fine, but there are two problems with the method:
It blocks the main thread while waiting for all the data to arrive. If you used this method in a real application, the user interface would become unresponsive while the data was fetched.
It has no way to callback if, for example, the webserver demands a username and password.
For these reasons, we typically use an NSURLConnection asynchronously. That is, we start it fetching and then await callbacks as the data arrives or the web server demands credentials or the fetch fails.
How do you get these callbacks? You supply the NSURLConnection with a helper object. When things happen, the connection sends messages to the helper object. What messages? The guy who wrote NSURLConnection made up a protocol – a list of method declarations – that the helper object can implement. Here are some of the methods in that protocol:
- (NSURLRequest *)connection:(NSURLConnection *)c willSendRequest:(NSURLRequest *)req redirectResponse:(NSURLResponse *)res; - (void)connection:(NSURLConnection *)sender didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)ch; - (void)connection:(NSURLConnection *)sender didReceiveData:(NSData *)data; - (void)connectionDidFinishLoading:(NSURLConnection *)sender; - (void)connection:(NSURLConnection *)sender didFailWithError:(NSError *)error; - (NSCachedURLResponse *)connection:(NSURLConnection *)sender willCacheResponse:(NSCachedURLResponse *)cachedResponse;
As you can see, an NSURLConnection lives a much richer life than an NSTimer. Now you are going to create an object that implements some or all of these methods and then introduce that object to the NSURLConnection as its helper object. In particular, the NSURLConnection has a pointer called delegate.
In main(), create an NSURLConnection and set the instance of Logger to be its delegate:
#import <Foundation/Foundation.h> #import "Logger.h" int main (int argc, const char * argv[]) { @autoreleasepool { Logger *logger = [[Logger alloc] init]; NSURL *url = [NSURL URLWithString: @"http://www.gutenberg.org/cache/epub/205/pg205.txt"]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; __unused NSURLConnection *fetchConn = [[NSURLConnection alloc] initWithRequest:request delegate:logger startImmediately:YES]; __unused NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:logger selector:@selector(sayOuch:) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] run]; } return 0; }
The instance of Logger will need an instance of NSMutableData to hold onto the bytes as they arrive. Add an instance variable to Logger.h:
#import <Foundation/Foundation.h> @interface Logger : NSObject { NSMutableData *incomingData; } - (void)sayOuch:(NSTimer *)t; @end
Now implement some of the delegate methods in Logger.m:
#import "Logger.h" @implementation Logger - (void)sayOuch:(NSTimer *)t { NSLog(@"Ouch!"); } // Called each time a chunk of data arrives - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { NSLog(@"received %lu bytes", [data length]); // Create a mutable data if it doesn't already exist if (!incomingData) { incomingData = [[NSMutableData alloc] init]; } [incomingData appendData:data]; } // Called when the last chunk has been processed - (void)connectionDidFinishLoading:(NSURLConnection *)connection { NSLog(@"Got it all!"); NSString *string = [[NSString alloc] initWithData:incomingData encoding:NSUTF8StringEncoding]; incomingData = nil; NSLog(@"string has %lu characters", [string length]); // Uncomment the next line to see the entire fetched file // NSLog(@"The whole string is %@", string); } // Called if the fetch fails - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { NSLog(@"connection failed: %@", [error localizedDescription]); incomingData = nil; } @end
Notice that you didn’t implement all the methods in the protocol – just the ones that you cared about.
Build and run the program. You should see that the data comes from the web server in reasonable sized chunks. Eventually, the delegate gets informed that the fetch is complete.
Here are the rules, so far, for callbacks: When sending one callback to one object, Apple uses target-action. When sending an assortment of callbacks to one object, Apple uses a helper object with a protocol. (We’ll talk more about protocols in the next chapter.) These helper objects are typically called delegate or data source.
What if the callback needs to go to multiple objects?