Home > Articles > Programming > C/C++

  • Print
  • + Share This
This chapter is from the book

Protocols

Chapter 1 introduced the notion of delegates. Delegates implement details that cannot be determined when a class is first defined. For example, a table knows how to display rows of cells, but it can’t know what to do when a cell is tapped. The meaning of a tapped row changes with whatever application implements that table. A tap might open another screen, send a message to a web server, or perform any other imaginable result. Delegation lets the table communicate with a smart object that is responsible for handling those taps but whose behavior is written at a completely separate time from when the table class itself is created.

Delegation basically provides a language that mediates contact between an object and its handler. A table tells its delegate “I have been tapped,” “I have scrolled,” and other status messages. The delegate then decides how to respond to these messages, producing updates based on its particular application semantics.

Data sources operate the same way, but instead of mediating action responses, data sources provide data on demand. A table asks its data source, “What information should I put into cell 1 and cell 2?” The data source responds with the requested information. Like delegation, data sourcing lets the table place requests to an object that is built to understand those demands.

In Objective-C, both delegation and data sourcing are produced by a system called protocols. Protocols define a priori how one class can communicate with another. They contain a list of methods that are defined outside any class. Some of these methods are required. Others are optional. Any class that implements the required methods is said to conform to the protocol.

Defining a Protocol

Imagine, if you would, a jack-in-the-box toy. This is a small box with a handle. When you turn the crank, music plays. Sometimes a puppet (called the “jack”) jumps out of the box. Now imagine implementing that toy (or a rough approximation) in Objective-C. The toy provides one action, turning the crank, and there are two possible outcomes: the music or the jack.

Now consider designing a programmatic client for that toy. It could respond to the outcomes, perhaps, by gradually increasing a boredom count when more music plays or reacting with surprise when the jack finally bounces out. From an Objective-C point of view, your client needs to implement two responses: one for music, another for the jack. Here’s a client protocol you might build:

@protocol JackClient <NSObject>
- (void) musicDidPlay;
- (void) jackDidAppear;
@end

This protocol declares that to be a client of the toy, you must respond to music playing and the jack jumping out of the box. Listing these methods inside an @protocol container defines the protocol. All the methods listed here are required unless you specifically declare them as @optional, as you read about in the next sections.

Incorporating a Protocol

Next, imagine designing a class for the toy itself. It offers one action, turning the crank, and requires a second object that implements the protocol, in this case called client. This class interface specifies that the client needs to be some kind of object (id) that conforms to the JackClient protocol (<JackClient>). Beyond that, the class does not know at design time what kind of object will provide these services.

@interface JackInTheBox : NSObject
{
    id <JackClient> client;
}
- (void) turnTheCrank;
@property (retain)      id <JackClient> client;
@end

Adding Callbacks

Callbacks connect the toy class to its client. Since the client must conform to the JackClient protocol, you can send jackDidAppear and musicDidPlay messages to the object and they will compile without error. The protocol ensures that the client implements these methods. In this code, the callback method is selected randomly. The music plays approximately nine out of every ten calls, sending musicDidPlay to the client.

- (void) turnTheCrank
{
    // You need a client to respond to the crank
    if (!self.client) return;

    // Randomly respond to the crank turn
    int action = random() % 10;
    if (action < 1)
        [self.client jackDidAppear];
    else
        [self.client musicDidPlay];
}

Declaring Optional Callbacks

Protocols include two kinds of callbacks: required and optional. By default, callbacks are required. A class that conforms to the protocol must implement those methods or they produce a compiler warning. You can use the @required and @optional keywords to declare a protocol method to be of one form or the other. Any methods listed after an @required keyword are required; after an @optional keyword, they are optional. Your protocol can grow complex accordingly.

@protocol JackClient <NSObject>
- (void) musicDidPlay; // required
@required
- (void) jackDidAppear; // also required
@optional
- (void) nothingDidHappen; // optional
@end

In practice, using more than a single @optional keyword is overkill. The same protocol can be declared more simply. When you don’t use any optional items, skip the keyword entirely. Notice the <NSObject> declaration here. It’s required to effectively implement optional protocols. It says that a JackClient object conforms to and will be a kind of NSObject.

@protocol JackClient <NSObject>
- (void) musicDidPlay;
- (void) jackDidAppear;
@optional
- (void) nothingDidHappen;
@end

Implementing Optional Callbacks

Optional methods let the client choose whether to implement a given protocol method. They reduce the implementation burden on whoever writes that client but add a little extra work to the class that hosts the protocol definition. When you are unsure whether a class does or does not implement a method, you must test before you send a message. Fortunately, Objective-C and the NSObject class make it easy to do so:

// optional client method
if ([self.client  respondsToSelector: @selector(nothingDidHappen)])
    [self.client nothingDidHappen];

NSObject provides a respondsToSelector: method, which returns a Boolean YES if the object implements the method or NO otherwise. By declaring the client with <NSObject>, you tell the compiler that the client can handle this method, which allows you to check the client for conformance before sending the message.

Conforming to a Protocol

Classes include protocol conformance in interface declarations. A view controller that implements the JackClient protocol announces it between angle brackets. A class might conform to several protocols. Combine these within the brackets, separating protocol names with commas:

@interface TestBedViewController :
    UIViewController <JackClient>
{
    JackInTheBox *jack;
}
@property (retain) JackInTheBox *jack;
@end

Declaring the JackClient protocol lets you assign the host’s client property. The following code compiles without error because the class for self was declared in conformance with JackClient:

self.jack = [JackInTheBox jack];
jack.client = self;

Had you omitted the protocol declaration in your interface, this assignment would produce an error at compile time.

Once you include that protocol between the angle brackets, you must implement all required methods in your class. Omitting any of them produces the kind of compile-time warnings shown in Figure 3-3. The compiler tells you which method is missing and what protocol that method belongs to.

Figure 3-3

Figure 3-3. You must implement all required methods to conform to a protocol. Objective-C warns about incomplete implementations.

The majority of protocol methods in the iOS SDK are optional. Both required and optional methods are detailed exhaustively in the developer documentation. Note that protocols are documented separately from the classes they support. For example, Xcode documentation provides three distinct UITableView reference pages: one for the UITableView class, one for the UITableViewDelegate protocol, and another for the UITableViewDataSource protocol.

  • + Share This
  • 🔖 Save To Your Account