Nic Hubbard
Nic Hubbard

Reputation: 42139

UIViewController subclass calling methods

I have built a UIViewController that implements various UI features. It is intended for a user to subclass so that they can update it with a few methods such as setContents: and getContents.

Within my UIViewController I am calling a method when they tap a button that I want the user to implement in their subclass, showPicker.

I would like the user to be able to be able to control what they do in this method, so I figured that they should just implement it within their subclass:

- (void)showPicker {
    // Do what you want
}

In order to do this, I had to add a blank method of the same name in my original UIViewController, then add it in the header file as well.

What I want to do is exactly like how you can subclass UIViewController and then implement viewWillAppear: and do what you want in that method.

Is this the correct pattern to follow if I want to allow the user to control what happens when a button inside of my class is tapped? Am I doing this all wrong? Should I be using delegates?

Upvotes: 0

Views: 438

Answers (2)

Jonah
Jonah

Reputation: 17958

Creating a class designed to be subclassed will work and it isn't wrong (most frameworks depend on such classes). Whether or not this is actually the best option for you depends on how you expect the class to be used.

Subclassing makes sense if:

  • You want to provide a default implementation of these methods.
  • You want the subclass to be able to override and replace a default implementation by controlling if and when to call super.
  • This message is only relevant to the receiving class, for example if the resulting action usually interacts with the controllers private methods or properties. Exposing it to other objects would introduce tight coupling between them and violate the single responsibility principle.

On the other hand a delegate may be a better choice if:

  • This behavior is optional.
  • The message is part of a concern which is not the sending class' responsibility. Perhaps the controller has determined the users intent ("select this option") but acting on that intent is not its job. Note that an object can be its own delegate if appropriate (e.g. To provide a default behavior instead of defaultin to a nil delegate.)
  • It is reasonable for one object to respond to this message from many sources.
  • It is important not to override behavior on the sending object. "Must call super" is not a good pattern despite the number of times it appears in UIKit.

Blocks might also be a good fit for accepting small units of custom behavior without requiring additional classes.

Upvotes: 1

MattP
MattP

Reputation: 2075

If you want to emulate what UIViewController does with viewWillAppear:, you will need to add a do-nothing implementation of showPicker in your base class. UIViewController.h indicates that viewWillAppear: has a default implementation that does nothing.

Delegates would complicate both your base class and the subclasses, without really simplifying much in return. They are really closer to a notification than an abstract method.

Other options here are to do something along the lines of:

if ([self canPerformSelector:@selector(showPicker)]) {
  ...
}

It doesn't give as much compile-time safety for my liking, but it is an alternative and keeps most of the responsibility in the base class. But I find that it isn't as discoverable as an empty method implementation in your base class.

If you don't want empty implementations for abstract methods in your base class, protocols may be a better fit, balancing discoverability and compile-time safety.

@protocol PickerProtocol <NSObject>
- (void)showPicker;
@end

@interface SomeBaseClass : UIViewController
  ...
@end

@implementation SomeBaseClass

- (void)someBaseClassMethod {
  if ([self conformsToProtocol:@protocol(Picker)]) {
    [(id<PickerProtocol>)self showPicker];
  }
}

@end

Your subclasses opt-in by declaring that they have implemented the protocol, allowing the compiler to warn if you didn't implement all @required methods.

@interface MySubClass <PickerProtocol>
  ...
@end

@implementation MySubClass

- (void)showPicker {
  ...
}

@end

This allows the compiler to show warnings and code completion. While it isn't as simple as empty base class methods, it does allow subclasses to explicitly declare that they offer some behavior.

If it's only a method here or there, I would use the empty method approach. If you have groups of methods that should be implemented together, I would use protocols.

Upvotes: 1

Related Questions