Tuesday, October 18, 2011

Implementing Objective-C Protocol Inheritance, Usually For Delegates

In a previous post, I explained why an Objective-C protocol might not implement respondToSelector:. Prior to Xcode 4.x, Apple's protocol template code didn't include the <NSObject> protocol in custom protocol definitions. This let to believe that you couldn't setup inheritance chains for protocols, but but you can!

This is usually important where you have a UIView subclass that's the base class for a bunch of subclasses. All instances must support the same message back to a delegate pattern, but subclasses might need to add more delegate callbacks than the base class should define.

Consider this class & protocol definition & class implementation:

@class VehicleView;

@protocol VehicleViewDelegate <NSObject>
- (void)vehicleView:(VehicleView *)vehicleView didTapToStartEngine:(Engine *)engine;

@optional
- (void)vehicleView:(VehicleView *)vehicleView didTapToOpenWindow:(VehicleWindow *)vehicleWindow;

@end

@interface VehicleView : NSObject {
 id<VehicleViewDelegate> _delegate;
 NSArray *_engines;
 NSArray *_vehicleWindows;
}

@property (non atomic, assign) id<VehicleViewDelegate> delegate;
@property (nonatomic, retain) NSArray *engines;
@property (nonatomic, retain) NSArray *vehiclesWindows;

@end

Now the implementation

@implementation VehicleView

@synthesize delegate = _delegate, engines = _engines, vehicleWindows = _vehicleWindows;

//Lots of other stuff to implement!

@end

What you can see there is a pretty typical class implementing a protocol then declaring a delegate instance variable and property pattern. But what does this look like when we want to subclass?

@class TruckView;

@protocol TruckViewDelegate <VehicleViewDelegate>

@optional
- (void)trunkView:(TruckView *)truckView didTapUseFourByFourMode;

@end

@interface TruckView: VehicleVIew {
}

//Subclass specific implementation

@end

Notice there's no delegate definition, what gives? That's true, but we are going to reuse our base class' instance variable for this:

@implementation TruckView

#pragma mark - Properties

- (id<TruckViewDelegate>)delegate
{
 return _delegate;
}

- (void)setDelegate:(id<TruckViewDelegate>)delegate
{
 if(_delegate != delegate) {
  _delegate = delegate;
 }
}

@end

So what's going on? The TruckView subclass overrode VehicleView's syntactic sugar property method declarations for delegate with the protocol definition that it wanted, but reused the base class instance variable. This allows TruckView to verify it has a pointer which implements the protocol, eliminates any dual-protocol declarations and management in TruckView, and maximizes protocol definition reuse.

Next time, I'll show how to optimize detection of @optional protocol methods so setting up @protocol inheritance hierarchies don't slow down performance.