Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Objective-C.Programming.pdf
Скачиваний:
14
Добавлен:
21.02.2016
Размер:
8.64 Mб
Скачать

24

Callbacks

Thus far, your code has been the boss. It has been sending messages to standard Foundation objects, like instances of NSString and NSArray, and telling them what to do. Thus far, your programs have run and exited in milliseconds.

You have been living a sweet and simple existence. In the real world, applications run for hours and your objects act as slaves to the stream of events pouring in from the user, the clock, the network, etc.

In a real-world application, there needs to be an object that waits for events like mouse movements, touch events, timers, and network activity. On Mac OS X and iOS, this object is an instance of NSRunLoop. The run loop sits and waits, and when something happens, it sends a message to another object.

We say that when something happens, the run loop causes a callback to occur. For Objective-C programmers, there are three forms that a callback can take. (Because these are very general ideas, I am going to use x for “a specific something” that happens. I’ll fill in the details in the sections that follow.)

Target-action: Before the wait begins, you say “When x happens, send this particular message to that particular object.” The object receiving the message is the target. The selector for the message is the action.

Helper objects: Before the wait begins, you say “Here is a helper object that conforms to your protocol. Send it messages when things happen.” (More on protocols in Chapter 25.) Helper objects are often known as delegates or data sources.

Notifications: There is an object called the notification center. Before the wait begins, you say to the notification center “This object is waiting for these sorts of notifications. When one of those notifications arrives, send the object this message.” When x happens, an object posts a notification to the notification center, and the center forwards it on to your object.

In this chapter, you will implement all three types of callbacks and learn which to employ in which circumstances.

Target-action

Timers use a target-action mechanism. You create a timer with a delay, a target, and an action. After that delay, the timer sends the action message to its target.

You are going to create a program with a run loop and timer. Every two seconds, the timer will send the action message to its target. You will create a class, and an instance of that class will be the target.

157

Chapter 24 Callbacks

Figure 24.1 Logger is the target of the NSTimer

In Xcode, create a new project: a Foundation Command Line Tool named Callbacks. As a goof, first you are just going to get a run loop and start it running. Edit main.m:

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[])

{

@autoreleasepool {

[[NSRunLoop currentRunLoop] run];

}

return 0;

}

Build and run the program. Notice that the method run never returns. The run loop is in an infinite loop waiting for something to happen. You’ll need to terminate the program. (Choose Product Stop.)

Now you are going to create a custom class to act as the target of the timer. Create a new file: an Objective-C class called Logger that is a subclass of NSObject. (Remember, to get to the class template, choose File New New File....) In Logger.h, declare the action method:

#import <Foundation/Foundation.h>

@interface Logger : NSObject

- (void)sayOuch:(NSTimer *)t;

@end

Notice that the action method takes one argument – the object that is sending the action message. In this case, it is the timer object.

Implement a simple sayOuch: method in Logger.m:

#import "Logger.h"

@implementation Logger

- (void)sayOuch:(NSTimer *)t

{

NSLog(@"Ouch!");

}

@end

At this point, we need to take a short detour and discuss selectors. Remember that when you send a message to an object, the object’s class is asked if it has a method with that name. The search goes up the inheritance hierarchy until a class responds with “Yeah, I’ve got a method with that name.”

158

Target-action

Figure 24.2 The search for a method with the right name

As you can imagine, this search has to happen very, very quickly. If we used the actual name of the method (which could be very long), method lookup would be really slow. To speed things up, the compiler assigns a unique number to each method name it encounters. At runtime, we use that number instead of the method name.

Figure 24.3 How it really works

That unique number that represents a particular method name is known as a selector. To create a timer that sends the message sayOuch: to our Logger, you must ask the compiler to look up its selector. This is done with the @selector compiler directive.

In main.m, create an instance of Logger and make it the target of an instance of NSTimer. Set the action to be the selector for sayOuch:.

159

Chapter 24 Callbacks

#import <Foundation/Foundation.h>

#import "Logger.h"

int main (int argc, const char * argv[])

{

@autoreleasepool {

Logger *logger = [[Logger alloc] init];

NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:logger

selector:@selector(sayOuch:)

userInfo:nil

repeats:YES];

[[NSRunLoop currentRunLoop] run];

}

return 0;

}

Build and run the program. (You will get an unused variable warning. Ignore it for now.) You should see Ouch! appear in the console every 2 seconds.

Note that the colon is part of the selector. @selector(sayOuch) is not equal to @selector(sayOuch:).

Take a second look at the warning from the compiler. It is saying, “Hey, you created the variable timer, but didn’t use it.” In some settings, like this one, you need to flag a variable as purposefully unused to silence the warnings. This is done with the __unused modifier. Add that now.

__unused NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:logger

selector:@selector(sayOuch:)

userInfo:nil

repeats:YES];

Build it again, and the warning should be gone.

Helper objects

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

160

Helper objects

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.

Figure 24.4 Logger is the delegate of the NSURLConnection

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"];

161

Chapter 24 Callbacks

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!");

162

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]