fbpx

Blogs from the Ranch

< Back to Our Blog

Protocols Part 3 : Adopting Protocols in Class Extensions

Avatar

Mark Dalrymple

One of the directions Apple is taking in Objective C that I’ve come to really like is the migration of stuff out of header files. I’m a firm believer that header files should only contain the public programming interface, along with any bookkeeping the compiler absolutely has to have And nothing else. Anything that doesn’t contribute to a person’s understanding of how to use your class shouldn’t be in there.

When I first started programming iOS I didn’t like the explicit protocols for data sources and delegates. I could understand why Apple does it, to make it explicit that “Hey! I know what I’m doing when I say I’m going to be a data source.” Things are much more explicit than what you get with an informal protocol, which is just a category. But it made the header feel kind of junky.

Say that one of my teammates on the BigLunch team wrote a view controller that lets the user choose a sandwich. It has a table view for picking the sandwich type (grinder, sub, hero, torpedo) and a spinner-picker for sandwich fillings (tofurkey, spam). The header file would look like this:

// BNRSandwichChooser.h
@interface BNRSandwichChooser : UIViewController <UITableViewDataSource, 
                                                  UITableViewDelegate, 
                                                  UIPickerViewDataSource, 
                                                  UIPickerViewDelegate>
- (BNRSandwich *) chooseLunch;

@end // BNRSandwichChooser

The table view and picker control are just implementation details. I, as a programming using this view controller, don’t care that it uses these particular UI objects. I just want to let the user choose lunch, and then go and do other stuff with their lunch choice. But, the BNRSandwichChooser needs to adopt the protocols somewhere so that the compiler won’t complain when setting the delegate:

- (void) viewDidLoad {
    self.tableView.datasource = self;
} // viewDidLoad

If the lunch chooser implementation changes to a text field or a web view, I don’t want my code to have to be recompiled because the header changed.

When Apple added class extensions, that bit of syntax that looks like a nameless category declaration, they also added the ability to to conform to protocols there:

// BNRSandwichChooser.m
#import "BNRSandwichChooser.h"

@interface BNRSandwichChooser () <UITableViewDatasource,
                                  UIPickerViewDataSource, ...>
@end

@implementation BNRSandwichChooser
...
@end

Leaving the interface nice and minimal:

@interface BNRSandwichChooser : UIViewController
- (BNRSandwich *) chooseLunch;
@end

Fixing the Map

If you remember last time, I showed a world map that used a delegate to get information from another class, and also to inform it that stuff happened. Here is what the header looked like:

#import <Cocoa/Cocoa.h>

<b>#import "BNRWorldMapView.h"</b>

@interface BNRAppController : NSObject <NSApplicationDelegate, 
                                        <b>BNRWorldMapViewDelegate</b>>
@property (unsafe_unretained) IBOutlet NSWindow *window;
@property (weak) IBOutlet BNRWorldMapView *worldMap;
@end // BNRAppController

It is kind of junky, having to include the map header (forcing a recompile of BNRAppController if you change a comment in BNRWorldMapView.h), and adopt the protocol. To clean up the header, remove the import and the adoption, and add a @class to forward-declare the world map view so Interface Builder will know what type to target a connection to;

// BNRAppController.h
#import <Cocoa/Cocoa.h>

@class BNRWorldMapView;

@interface BNRAppController : NSObject <NSApplicationDelegate> 
@property (unsafe_unretained) IBOutlet NSWindow *window;
@property (weak) IBOutlet BNRWorldMapView *worldMap;
@end // BNRAppController

And then in the implementation, I’d add the adoption to the class extension:

// BNRAppController.m
@interface BNRAppController () <b><BNRWorldMapViewDelegate></b>
@property  NSMutableSet *selectedCountries;
@end // BNRAppController

The takeaway? Unless the protocol adoption has to be public for other classes to compile correctly, I now put all implementation-detail protocol adoptions into a class extension in the implementation file. This keeps the implementation details nice and hidden, and keeps the interface clean.

Avatar

Mark Dalrymple

Not Happy with Your Current App, or Digital Product?

Submit your event

Let's Discuss Your Project

Let's Discuss Your Project