4
\$\begingroup\$

Is there something as "they have too little in common to use a superclass"?

In my case, doing iOS programming, in a project, every screen has the same background. So I created a superclass which only has:

.h

@property (nonatomic) IBOutlet UIImageView *backgroundImageView;

.m

- (void)viewDidLoad {
    [super viewDidLoad];

    UIImage *backgroundImage = ...;
    [self.backgroundImageView setImage:backgroundImage];
}

Is this too little to use a superclass for?

If I didn't add it here, I'd have about 10 view controllers where I would have to do this. (Obviously code duplication).

\$\endgroup\$
  • \$\begingroup\$ I'm not familiar with programming directly with iOS (assuming in XCode with Objevtice-C here). Can you have more than one scene being displayed at once? \$\endgroup\$ – TimeEchoes Nov 4 '14 at 15:58
  • \$\begingroup\$ @TimeEchoes A view controller only has one view, but an app can display multiple views at once, and they can all have their own controllers. Not sure of the relevance of your question however. \$\endgroup\$ – nhgrif Nov 4 '14 at 19:12
8
\$\begingroup\$

The short answer is, no, this is not too little code to "extract" into a super class.

If you intend for this bit of code to always be the same throughout every use of this class and its subclasses, then it's absolutely appropriate to put the code in the superclass. The primary point is, here, if we ever decide to change something about this code, we can change it in once place and have this change applied across all instances.

With that said, however, I want to point out some problems.


As a start, we have no guarantee that this code will actually run. If our subclass implements viewDidLoad and fails to call to super, now our code doesn't run. There's still no way to make this guarantee, but there is a way to make a reminder for ourselves.

As this StackOverflow answer outlines, the NS_REQUIRES_SUPER directive will throw a warning when subclass implement the method without calling the super method. So we should add the following to our .h file:

- (void)viewDidLoad NS_REQUIRES_SUPER;

Now, a subclass of this class which implements viewDidLoad will throw a compiler warning until [super viewDidLoad]; is added to its viewDidLoad implementation.


There's problems still however. For starters, as a rule, I don't like IBOutlets in my header file. There's absolutely no reason for these to be public. But in this particular case, we shouldn't have an IBOutlet at all. Using an image view set up in interface builder and requiring it be hooked up to this outlet will supremely complicate things.

First, not only do we have to remember to call to super, which I already address. But we also have to remember to add the image view. And we have to remember to hook the image view up as an outlet. Trouble is though, we can't (or shouldn't) hook it up to our current class. We should open this, the super class, in assistant editor and hook it up to that. Problem is, assistant editor won't automatically load super class's in the assistant window... so that's kind of a pain.

I'm a huge proponent of using interface builder. It does a lot to vastly simplify our written code, but in this case, it doesn't suffice for producing simplified results.

We should remove this outlet altogether and not worry in the slightest about relying on the subclass setting up the image view in interface builder and hooking it up properly. The whole point of subclassing is minimizing redundancy, and so far, we're only half way there.

(Moreover, what guarantee do we have that every subclass of this class will even use interface builder at all? View Controllers don't require a corresponding interface builder representation--the view can be set up entirely programmatically.)

We need to change our viewDidLoad code to manually create an image view with an image and load it as the background.


The number one problem we'll run into in manually creating and adding the image view to the view controller is getting it appropriately as the background. We can't be sure whether the subclass will do it's set up first then call super, or call super then do its set up (in case you're wondering, in viewDidLoad, it's appropriate to call to super FIRST, then add your implementation). Moreover, the view controller will almost certainly have views added on it in interface builder if the developer is using interface builder.

So, we need to add the view as the bottom-most view in the view controller's view's subviews.

This might be a good implementation:

- (void)viewDidLoad {
    [super viewDidLoad];

    // Set up image view
    UIImage *backgroundImage = [UIImage imageNamed:@"background"];
    UIImageView *backgroundView = [[UIImageView alloc] initWithImage:backgroundImage];
    [backgroundView setTranslatesAutoresizingMaskIntoConstraints:NO];
    // aspect fill may be preferred
    backgroundView.contentMode = UIViewContentModeScaleAspectFit; 

    // Add background view as back-most view
    [self.view insertSubview:backgroundView atIndex:0];

    // Set up auto layout constraints so this view controller is applicable
    // on any device in any rotation
    NSDictionary *views = NSDictionaryOfVariableBindings(backgroundView);

    NSArray *verticalConstraints = 
        [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[backgroundView]-0-|"
                                                options:0
                                                metrics:nil
                                                  views:views];
    NSArray *horizontalConstraints = 
        [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[backgroundView]-0-|"
                                                options:0
                                                metrics:nil
                                                  views:views];

    [self.view addConstraints:verticalConstraints];
    [self.view addConstraints:horizontalConstraints];
}

I'm not particularly a fan of setting up UI in code (especially auto layout). I much, much prefer interface builder. But in some cases, it is absolutely necessary to get it right.

This auto layout code may seem bulky and unnecessary if your current app is just for iPads and just for a single orientation. But as soon as you want to subclass this view controller into an app that rotates or is made for iPhones, or is a universal app, you'll be glad to have this auto layout code.


As one final comment, I might make a UIImage property in the .h file so that the background image could be changed.

\$\endgroup\$
  • \$\begingroup\$ Absolutely love your response. I never knew about the NS_REQUIRES_SUPER macro. \$\endgroup\$ – Lord Zsolt Nov 5 '14 at 8:40
3
\$\begingroup\$

On the face of it, extracting duplicated code is reason enough to introduce the superclass. Depending on your project, you may eventually find more opportunity for common code to be extracted into the viewDidLoad method.

Obviously, you would cause an additional [super viewDidLoad] call to be made due to the extra class in the inheritance hierarchy. Assuming you're using +imageNamed, the first call in the process would cause the image to be loaded and cached, and successive calls use the cached version, which would cause the extra call to have a (relatively) significant cost. In terms of absolute performance, however, I doubt the additional call would make a significant difference, since it is only called once per view controller instance.

If, on the other hand, you're loading from a file each time, the cost of the image loading would most likely overshadow the cost incurred by [super viewDidLoad] such that it would be negligible.

In short, the maintainability gains from extracting the shared code is far greater than the performance win of avoiding an additional one-time method call.

\$\endgroup\$
  • 1
    \$\begingroup\$ I don't understand the worry about the expense of loading the image. The image must be loaded exactly once per view controller that is a subclass of this class. It doesn't matter how far down the hierarchy it is. Loading the image happens exactly once... \$\endgroup\$ – nhgrif Nov 5 '14 at 0:46
  • \$\begingroup\$ @nhgrif Yes, 100%.I was approaching it from answering "Is this too little to use a superclass for?" in the question. That is, "I'm introducing another class and super call just to load an image; is it worth it?". \$\endgroup\$ – slyfox Nov 5 '14 at 6:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.