Intro to Object-Oriented Design: Part 1/2

Ellen Shapiro
Object-oriented design

Object-oriented programmers love vehicle hierarchies.

Update note: Check out the latest version of this tutorial, updated for iOS 8 and Swift! Intro to Object-Oriented Design in Swift.

A huge piece of the programming puzzle in working with Cocoa and Objective-C is Object-Oriented Programming. Almost all modern programming languages use this approach, and wrapping your head around its concepts and patterns can be incredibly helpful when reading and writing code.

Underneath the relation between UITableView and UIScrollView or NSString and NSMutableString are the fundamental concepts of object-oriented design. By understanding these concepts you’ll have a better grasp on why things are organized the way they are in Cocoa and Cocoa Touch, and you’ll be a more thoughtful programmer when writing your own apps and frameworks.

In this series you’ll learn more about object-oriented design, including the following concepts:

  • The Basics Of Objects
  • Inheritance
  • Model-View-Controller
  • Polymorphism
  • Common Object-Oriented Patterns

This series is designed for developers who are fairly new to programming overall – much like I was when I first started out. You likely haven’t worked with other languages extensively, and you’re not quite sure why everything is being done in a particular way.

This tutorial will cover object-oriented design principles rather than specific syntax, so you should already know the basics of Objective-C and Xcode before reading on. If you need a refresher on the basics, check out our other Beginning Objective-C tutorials.

Getting Started

In order to try and understand some of these concepts in a more concrete manner, you’ll build an application called Vehicles. This uses one of the most common metaphors for translating real-world items into virtual objects: the “vehicle”, which could be a bicycle, a car, or really anything with wheels.

For instance, this is a vehicle:

Kleinwagen_16

But so is this:

Motorcycle on a white background

Or this:

B

Or this:

European 18-wheeler with canvas trailer

In this part of the tutorial, you’ll create a data model using basic object-oriented techniques to represent all of these vehicles, and a simple application which implements the data model and displays vehicle data to the user.

Download the starter project, which contains a basic framework for the application you’ll use to learn about Object-Oriented Programming.

The Basics Of Objects

In object-oriented programming, the basic goal is to break down the characteristics of a “thing” to create an object or objects that describe what that thing is and what that thing does.

Sometimes, as with vehicles, your “thing” has a real-world equivalent. Sometimes it doesn’t, as with the many different types of UIViewController objects. For the sake of simplicity, you’ll start out by creating objects that have real-world analogs.

In order to answer the question of what a “thing” is, you have to first determine what its defining characteristics are.

Other languages will refer to this as a “field”, a “member”, or even just a “variable”. However, in Objective-C the defining characteristics of an object are shown by its properties.

Think about the generic concept of a “vehicle” for a moment — something that describes all of the photos above. What common characteristics of a “vehicle” spring to mind?

  • It has a number of wheels greater than zero.
  • It has some sort of power source, whether human, gas, electric, or hybrid, which makes it move.
  • It has a brand*, like Ford, Chevy, Harley-Davidson, or Schwinn.
  • It has a model name like Mustang, Corvette, Sportster, or Fastback.
  • It has a year associated with its manufacture.

*- this is sometimes referred to in cars and trucks as a “Make”, but we’ll refer to it as a “brand” across the board for clarity.

Now that you have the basic characteristics of a vehicle, you can create an Object with these characteristics.

There are two files in the starter project: Vehicle.h and Vehicle.m, which together represent a subclass of NSObject. In a moment you’ll read more about subclasses and what they are.

Add the following code to Vehicle.h after the @interface line:

@property (nonatomic, assign) NSInteger numberOfWheels;
@property (nonatomic, copy) NSString *powerSource;
@property (nonatomic, copy) NSString *brandName;
@property (nonatomic, copy) NSString *modelName;
@property (nonatomic, assign) NSInteger modelYear;

These property declarations describe the characteristics of the object you want to keep track of.

A Minor Digression: Under The Hood With Properties

Whenever you declare a @property in Xcode 4.4 and above, Xcode automatically synthesizes a backing instance variable, a getter method, and a setter method for that property. This saves a ton of boilerplate code. If there were no automatic synthesis, you would have to write all of the following code for every single instance variable:

@interface Vehicle() {
    NSString *_brandName;
}
@end

@implementation Vehicle

//Setter method
-(void)setBrandName:(NSString *)brandName
{
    _brandName = [brandName copy];
}

//Getter method
-(NSString *)brandName
{
    return _brandName;
}

@end

Not having to add this code for every single @property keeps your code cleaner and much more readable. This also means that you can access a @property in a couple of different ways:

  • someVariableName = self.brandName; goes behind the scenes and calls the [self brandName]; getter method that was synthesized for you, returns whatever value is stored in the _brandName instance variable, and assigns it to someVariableName.
  • self.brandName = @"Some Brand Name"; also goes behind the scenes and calls the [self setBrandName:@"Some Brand Name"]; setter method, which in turn sets the value of the _brandName instance variable to @"Some Brand Name".

Describing the Object

On to the second critical question for every object — what exactly does the object do?

A programmatic description of what an object does is almost universally called a method. Think about the common actions of the vehicles in the photos above:

  • It can go forward
  • It can go backward
  • It can stop
  • It can turn
  • It can change gears
  • It can make some sort of noise (e.g. a horn or a bell)

Most often, you’d be using methods with a void return type: for example, -(void)nameOfMethod. This is useful when you don’t need to get any information back from the method and you simply want the method to execute. However, to make it a little easier to display what’s happening in your app, you’re going to use some methods that return NSString objects.

A Minor Digression: Class Methods vs. Instance Methods

You’ve probably noticed when writing code that some methods have a + and some methods have a in front of them. These indicate whether a method is a Class method or an Instance method.

The simplest way to think of the difference is like a schematic blueprint in the physical world: There’s only one blueprint for something, but using that blueprint, you can make many different copies.

A class method, represented by the + symbol, is an action that can be performed with that blueprint, but without creating a specific copy of the object from that blueprint. For example, NSString has the stringWithFormat: class method to create new string objects.

An instance method, represented by the symbol, requires a specific copy of the object created using that blueprint to perform any action. For example, the NSString instance @"Hello There" has an instance method lowercaseString that would return @"hello there". A class method lowercaseString wouldn’t make any sense since that’s just the blueprint for a string – there’s no actual text to lower case!

Adding Basic Methods to Your Class

Go to Vehicle.h and add the following method declarations to the header file, just below the properties you added earlier, and before @end:

//Basic operation methods
-(NSString *)goForward;
-(NSString *)goBackward;
-(NSString *)stopMoving;
-(NSString *)changeGears:(NSString *)newGearName;
-(NSString *)turn:(NSInteger)degrees;
-(NSString *)makeNoise;

A method declaration in a header file is public – it tells other objects looking at your class, “Here’s what I’m going to be able to do”, but it doesn’t give any details of how it’s done. To do that, open Vehicle.m and add the following implementations, or actual code to execute, for each of these methods:

-(NSString *)goForward
{
    return nil;
}

-(NSString *)goBackward
{
    return nil;
}

-(NSString *)stopMoving
{
    return nil;
}

-(NSString *)turn:(NSInteger)degrees
{
    //Since there are only 360 degrees in a circle, calculate what a single turn would be.
    NSInteger degreesInACircle = 360;
    
    if (degrees > degreesInACircle || degrees < -degreesInACircle) {
        //The % operator returns the remainder after dividing. 
        degrees = degrees % degreesInACircle;
    }
    
    return [NSString stringWithFormat:@"Turn %d degrees.", degrees];
}

-(NSString *)changeGears:(NSString *)newGearName
{
    return [NSString stringWithFormat:@"Put %@ into %@ gear.", self.modelName, newGearName];
}

-(NSString *)makeNoise
{
    return nil;
}

Most of this code is just the skeleton of setting up the methods; you'll fill in the implementation details later. The turn: and changeGears: methods have some logging output too, which will help you test that your methods are working before you continue any further in the tutorial.

Open AppDelegate.m, and add an import to the top of the file:

#import "Vehicle.h"

This will allow you to access the Vehicle class from your own code.

Next, replace the implementation of application:didFinishLaunchingWithOptions: with the following:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    Vehicle *vehicle = [[Vehicle alloc] init];
    //Test methods with implementations
    NSLog(@"Vehicle turn: %@", [vehicle turn:700]);
    NSLog(@"Vehicle change gears: %@", [vehicle changeGears:@"Test"]);
    
    //Test methods without implementations
    NSLog(@"Vehicle make noise: %@", [vehicle makeNoise]);
    NSLog(@"Vehicle go forward: %@", [vehicle goForward]);
    NSLog(@"Vehicle go backward: %@", [vehicle goBackward]);
    NSLog(@"Vehicle stop moving: %@", [vehicle stopMoving]);
    
    return YES;
}

Once the vehicle instance is instantiated, you'll call each of the instance methods and log the output to see what they do.

Build and run your app; you’ll see that all implemented strings return proper logs with the appropriate items filled in. But anywhere a property isn’t set or a method returns nil, you’ll see a (null), as follows:

Log output

You'll be using Inheritance to provide more specific implementations of each of these methods.

Inheritance

The basic concept of inheritance is similar to that of genetics: children inherit the characteristics of their parents.

However, it’s a lot more strict than real world genetics in a single-inheritance language like Objective-C. Rather than having two parents from whom you inherit a mix of characteristics, “child” classes, or subclasses, inherit all the characteristics of their “parent” classes, or superclasses.

NSObject, which Vehicle inherits from, is the lowest-level class you can use in Objective-C. It’s the parent class to almost all of the everyday Objective-C classes.

Note: There are some C structs like CGRect and CGSize that don't use NSObject as their parent class, since structs don’t really adhere to Object-Oriented programming. However, the vast majority of classes with the prefix NS or UI have NSObject as their parent class. Check out Apple's documentation to learn more about NSObject.

To see inheritance in action, create a subclass of Vehicle, “Car”. Go to File\New\File...\Cocoa Touch\Objective-C Class. Then, create a subclass of Vehicle called Car, as shown below:

Add Car

Now open Car.m add the following initialization method under the @implementation line:

- (id)init
{
  if (self = [super init]) {
    // Since all cars have four wheels, we can safely set this for every initialized instance
    // of a car. 
      self.numberOfWheels = 4;
  }
  return self;
}

This init implementation simply sets the number of wheels to 4.

Did you notice that you didn’t have to do anything extra to access the numberOfWheels property of the Vehicle parent class? That’s because by inheriting from the Vehicle class, Car already knows about all of Vehicle’s public properties and methods.

But what if you need more information to describe the car? Cars have more specific characteristics than just the number of wheels How many doors does it have? Is it a hatchback or does it have a normal trunk? Is it a convertible? Does it have a sunroof?

Well, you can easily add those new properties! Open Car.h and add the following new properties under the @interface line:

@property (nonatomic, assign) BOOL isConvertible;
@property (nonatomic, assign) BOOL isHatchback;
@property (nonatomic, assign) BOOL hasSunroof;
@property (nonatomic, assign) NSInteger numberOfDoors;

Overriding Methods

Now that you’ve added the appropriate additional properties, you can add one new method and override several methods from the superclass to provide full implementations of those methods.

Overriding a method means “taking a method declared in the superclass and creating your own implementation.” 

For example, when you add a new UIViewController object, it already comes with overridden methods for initWithNibName:bundle:, viewDidLoad, and didReceiveMemoryWarning.

When you override a method, you can do one of two things:

  1. Include a call to the [super method] to take advantage of everything happening higher up the inheritance chain, or
  2. Provide your own implementation from scratch.

In all of the UIViewController methods, you can tell that Apple wants you to call the [super method] - there’s some important stuff in there that needs to execute before your UIViewController subclass can do its work.

However, since most of the methods you're going to override in the Car class are returning nil, you can just create your own implementations. There's nothing useful in the superclass's implementation so there's no need to call it.

Open Car.m and add the following private method to simplify your superclass override:

#pragma mark - Private method implementations
- (NSString *)start
{
    return [NSString stringWithFormat:@"Start power source %@.", self.powerSource];
}

Some vehicles such as bicycles don't need to be started, but cars do! In this case, you're not publicly declaring start since it should only be called within the implementation.

Note: Even though a method is "private" and other objects and classes can't see it, that doesn't protect a subclass from overriding the method. You really can't stop something from going wrong in this case, but you should make notes in your app's documentation, just as Apple does.

Next, add the remaining superclass overrides:

#pragma mark - Superclass Overrides
- (NSString *)goForward
{
    return [NSString stringWithFormat:@"%@ %@ Then depress gas pedal.", [self start], [self changeGears:@"Forward"]];
}

- (NSString *)goBackward
{
    return [NSString stringWithFormat:@"%@ %@ Check your rear view mirror. Then depress gas pedal.", [self start], [self changeGears:@"Reverse"]];
}

- (NSString *)stopMoving
{
    return [NSString stringWithFormat:@"Depress brake pedal. %@", [self changeGears:@"Park"]];
}

- (NSString *)makeNoise
{
    return @"Beep beep!";
}

Now that you have a concrete, or fully implemented, subclass of Vehicle, you can start building out your Table View controller.

Building out the User Interface

In VehicleListTableViewController.m, add the following import to the top of the file, just below the import for Vehicle:

#import "Car.h"

Next, add the following method between didReceiveMemoryWarning and #pragma mark - Table View:

#pragma mark - Data setup
-(void)setupVehicleArray
{
    //Create a car.
    Car *mustang = [[Car alloc] init];
    mustang.brandName = @"Ford";
    mustang.modelName = @"Mustang";
    mustang.modelYear = 1968;
    mustang.isConvertible = YES;
    mustang.isHatchback = NO;
    mustang.hasSunroof = NO;
    mustang.numberOfDoors = 2;
    mustang.powerSource = @"gas engine";
    
    //Add it to the array
    [self.vehicles addObject:mustang];
    
    //Create another car.
    Car *outback = [[Car alloc] init];
    outback.brandName = @"Subaru";
    outback.modelName = @"Outback";
    outback.modelYear = 1999;
    outback.isConvertible = NO;
    outback.isHatchback = YES;
    outback.hasSunroof = NO;
    outback.numberOfDoors = 5;
    outback.powerSource = @"gas engine";
    
    //Add it to the array.
    [self.vehicles addObject:outback];
    
    //Create another car
    Car *prius = [[Car alloc] init];
    prius.brandName = @"Toyota";
    prius.modelName = @"Prius";
    prius.modelYear = 2002;
    prius.hasSunroof = YES;
    prius.isConvertible = NO;
    prius.isHatchback = YES;
    prius.numberOfDoors = 4;
    prius.powerSource = @"hybrid engine";
    
    //Add it to the array.
    [self.vehicles addObject:prius];

    //Sort the array by the model year
    NSSortDescriptor *modelYear = [NSSortDescriptor sortDescriptorWithKey:@"modelYear" ascending:YES];
    [self.vehicles sortUsingDescriptors:@[modelYear]];
}

This simply separates the data setup methods and adds the method to construct your vehicle array.

Find awakeFromNib and add this code to the end of the method:

  // Initialize the vehicle array
  self.vehicles = [NSMutableArray array];

  // Call the setup method
  [self setupVehicleArray];

  // Set the title of the View Controller, which will display in the Navigation bar.
  self.title = @"Vehicles";

The above method executes when your Storyboard finishes constructing the nib for a particular UIViewController at runtime. It calls the setupVehicleArray method you just created, and sets the title of the VehicleListTableViewController to reflect its contents.

Build and run your application; you’ll see something that looks like the following:

Three Cars

The numbers you see represent memory addresses and will be different, but everything else should look the same.

The good news is that these objects are being recognized as Car objects. The bad news is that what’s being displayed isn’t terribly useful. Take a look at what’s set up in the UITableViewDataSource method tableView:cellForRowAtIndexPath:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];

    Vehicle *rowVehicle = self.vehicles[indexPath.row];
    cell.textLabel.text = [rowVehicle description];
    return cell;
}

Here, you’re grabbing a UITableViewCell object then getting the Vehicle at the index in the self.vehicles array matching the row of the cell you're constructing. Next, you set that specific Vehicle’s description string as the text for the cell’s textLabel.

The string produced by the description method (which is inherited from NSObject) isn’t very human-friendly. You’ll want to define a method in Vehicle that describes what each Vehicle object represents in a way that is easy for your users to understand.

Go back to Vehicle.h and add the following new method declaration, below all the other basic method declarations, but above @end:

//Convenience method for UITableViewCells and UINavigationBar titles.
-(NSString *)vehicleTitleString;

Then, in Vehicle.m, add the following implementation, again below the basic method implementations:

#pragma mark - Convenience Methods
-(NSString *)vehicleTitleString
{
    return [NSString stringWithFormat:@"%d %@ %@", self.modelYear, self.brandName, self.modelName];
}

The method above takes three properties that should be used on every single Vehicle and uses them to describe the vehicle concisely.

Now, update VehicleListTableViewController's tableView:cellForRowAtIndexPath: method to use this new method on Vehicle as follows:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];

    Vehicle *rowVehicle = self.vehicles[indexPath.row];
    cell.textLabel.text = [rowVehicle vehicleTitleString];
    return cell;
}

Build and run your application; it should look a little nicer now:

Three Vehicles With Readable Titles

However, if you select a Vehicle from the list, all you'll see are the same elements visible in the storyboard, with none of the details from the Vehicle you selected:

Before Data Hookup Detail

What's up with that?

Open VehicleDetailViewController.m; you'll see that while the UI was constructed in the Storyboard and all the IBOutlets were already hooked up to save you some time fighting with AutoLayout, none of the data is hooked up.

Note: You'll notice that several of the IBOutlets are set up in the VehicleDetailViewController.m instead of in the .h file, as they normally would be.

If you have properties you don’t want to be publicly available to other classes, you can always add them to your .m file in the private implementation. This is the @interface that’s declared at the top of a .m file and noted by the parentheses after the class name. For example, UIViewController() would be the private implementation of UIViewController.

Any @property declared in that interface can still be accessed as an IBOutlet (if properly annotated as such) in your Storyboard and within your .m implementation file, but it won't be accessible to any unrelated classes or to subclasses of your class.

Hooking Your Data to Your View

To hook up the data, update configureView in VehicleDetailViewController.m to take advantage of the vehicle set by the segue as follows:

- (void)configureView
{
    // Update the user interface for the detail vehicle, if it exists.
    if (self.detailVehicle) {
        //Set the View Controller title, which will display in the Navigation bar.
        self.title = [self.detailVehicle vehicleTitleString];
        
        //Setup the basic details string based on the properties in the base Vehicle class.
        NSMutableString *basicDetailsString = [NSMutableString string];
        [basicDetailsString appendString:@"Basic vehicle details:\n\n"];
        [basicDetailsString appendFormat:@"Brand name: %@\n", self.detailVehicle.brandName];
        [basicDetailsString appendFormat:@"Model name: %@\n", self.detailVehicle.modelName];
        [basicDetailsString appendFormat:@"Model year: %d\n", self.detailVehicle.modelYear];
        [basicDetailsString appendFormat:@"Power source: %@\n", self.detailVehicle.powerSource];
        [basicDetailsString appendFormat:@"# of wheels: %d", self.detailVehicle.numberOfWheels];
        
        self.vehicleDetailsLabel.text = basicDetailsString;
    }
}

Build and run your application; select a vehicle from the TableView and you'll see that the detail view is now showing the correct title and details, as so:

Basic vehicle details

Model-View-Controller Encapsulation of Logic

iOS and many other modern programming languages have a design pattern known as Model-View-Controller , or MVC for short.

The idea behind MVC is that views should only care about how they are presented, models should only care about their data, and controllers should work to marry the two without necessarily knowing too much about their internal structure.

The biggest benefit to the MVC pattern is making sure that if your data model changes, you only have to make changes once.

One of the biggest rookie mistakes that programmers make is putting far too much of the logic in their UIViewController classes. This ties views and UIViewControllers too closely to one particular type of model, making it harder to reuse views to display different kinds of details.

Why would you want to do implement the MVC pattern in your app? Well, imagine that you wanted to add more specific details about a car to VehicleDetailViewController. You could start by going back into configureView and adding some information specifically about the car, as so:

//Car-specific details
[basicDetailsString appendString:@"\n\nCar-Specific Details:\n\n"];
[basicDetailsString appendFormat:@"Number of doors: %d", self.detailVehicle.numberOfDoors];

But you’ll notice that there’s one minor problem with this:

Error with Car properties

VehicleDetailsViewController only knows about the properties of the main Vehicle superclass; it doesn't know anything anything about the Car subclass.

There are a couple of ways you can fix this.

The one that seems the most immediately obvious is to just import Car.h, so that VehicleDetailViewController knows about the properties of the Car subclass. But that would mean having to add a ton of logic to handle all the properties for every single subclass.

Any time you catch yourself doing that, ask yourself: "is my view controller trying to do too much?"

In this case, the answer is yes. You can take advantage of inheritance to use the same method to supply the string to be displayed for the appropriate details for each subclass.

Creating Subclasses via Inheritance

First, add the following new method to Vehicle.h:

//Convenience method to get the vehicle's details.
-(NSString *)vehicleDetailsString;

That's the publicly-declared method that can be used from clients such as VehicleDetailsViewController. There's no need for them to know about each individual property; instead, they can just ask for vehicleDetailsString, receive a fully-formatted string, and use it.

Switch over to Vehicle.m and add the implementation:

-(NSString *)vehicleDetailsString
{
    //Setup the basic details string based on the properties in the base Vehicle class.
    NSMutableString *basicDetailsString = [NSMutableString string];
    [basicDetailsString appendString:@"Basic vehicle details:\n\n"];
    [basicDetailsString appendFormat:@"Brand name: %@\n", self.brandName];
    [basicDetailsString appendFormat:@"Model name: %@\n", self.modelName];
    [basicDetailsString appendFormat:@"Model year: %d\n", self.modelYear];
    [basicDetailsString appendFormat:@"Power source: %@\n", self.powerSource];
    [basicDetailsString appendFormat:@"# of wheels: %d", self.numberOfWheels];
    
    return [basicDetailsString copy];
}

The method is similar to what you added to VehicleDetailViewController.m, except it returns the generated string rather than display it somewhere directly.

Now, you can use inheritance to take this basic vehicle string and add the specific details for the Car class. Open Car.m and add the override implementation of vehicleDetailsString:

- (NSString *)vehicleDetailsString
{
    //Get basic details from superclass
    NSString *basicDetails = [super vehicleDetailsString];
    
    //Initialize mutable string
    NSMutableString *carDetailsBuilder = [NSMutableString string];
    [carDetailsBuilder appendString:@"\n\nCar-Specific Details:\n\n"];
    
    //String helpers for booleans
    NSString *yes = @"Yes\n";
    NSString *no = @"No\n";
    
    //Add info about car-specific features.
    [carDetailsBuilder appendString:@"Has sunroof: "];
    if (self.hasSunroof) {
        [carDetailsBuilder appendString:yes];
    } else {
        [carDetailsBuilder appendString:no];
    }
    
    [carDetailsBuilder appendString:@"Is Hatchback: "];
    if (self.isHatchback) {
        [carDetailsBuilder appendString:yes];
    } else {
        [carDetailsBuilder appendString:no];
    }
    
    [carDetailsBuilder appendString:@"Is Convertible: "];
    if (self.isConvertible) {
        [carDetailsBuilder appendString:yes];
    } else {
        [carDetailsBuilder appendString:no];
    }
    
    [carDetailsBuilder appendFormat:@"Number of doors: %d", self.numberOfDoors];
    
    //Create the final string by combining basic and car-specific details.
    NSString *carDetails = [basicDetails stringByAppendingString:carDetailsBuilder];
    
    return carDetails;
}

Car's version of the method starts by calling the superclass's implementation to get the vehicle details. It then builds the car-specific details string into carDetailsBuilder and then combines the two at the very end.

Now replace configureView in VehicleDetailViewController.m with the much simpler implementation below to display this string that you’ve created:

- (void)configureView
{
    // Update the user interface for the detail vehicle, if it exists.
    if (self.detailVehicle) {
        //Set the View Controller title, which will display in the Navigation bar.
        self.title = [self.detailVehicle vehicleTitleString];
        self.vehicleDetailsLabel.text = [self.detailVehicle vehicleDetailsString];
    }
}

Build and run your application; select one of the cars, and you should now be able to see both general details and car-specific details as shown below:

Basic and car-specific details

Your VehicleDetailViewController class now allows the Vehicle and Car classes to determine the data to be displayed. The only thing ViewController is doing is connecting that information up with the view!

The real power in this is evident when you create further subclasses of Vehicle. Start with a fairly simple one for a motorcycle.

Go to File\New\File\CocoaTouch\Objective-C Class, and create a new subclass of Vehicle called Motorcycle.

Since motorcycles can have either a deep booming engine noise, or a high-pitched whine of an engine noise, each Motorcycle object you create you should specify which type of noise it makes.

Add a single string property for the Engine Noise in Motorcycle.h just after the @interface line:

@property (nonatomic, strong) NSString *engineNoise;

Then, open Motorcycle.m. Add the following init method:

#pragma mark - Initialization
- (id)init
{
    if (self = [super init]) {
        self.numberOfWheels = 2;
        self.powerSource = @"gas engine";
    }
    
    return self;
}

Since all motorcycles have two wheels and are gas-powered (for the sake of this example, anything that's electric-powered would be considered a scooter, not a motorcycle), you can set up the number of wheels and power source when the object is instantiated.

Next, add the following methods to override all the superclass methods that will just return nil otherwise:

#pragma mark - Superclass Overrides
-(NSString *)goForward
{
    return [NSString stringWithFormat:@"%@ Open throttle.", [self changeGears:@"Forward"]];
}

-(NSString *)goBackward
{
    return [NSString stringWithFormat:@"%@ Walk %@ backwards using feet.", [self changeGears:@"Neutral"], self.modelName];
}

-(NSString *)stopMoving
{
    return @"Squeeze brakes.";
}

-(NSString *)makeNoise
{
    return self.engineNoise;
}

Finally, override the vehicleDetailsString method in order to add the Motorcycle-specific details to the vehicleDetailsString as shown below:

- (NSString *)vehicleDetailsString
{
    //Get basic details from superclass
    NSString *basicDetails = [super vehicleDetailsString];
    
    //Initialize mutable string
    NSMutableString *motorcycleDetailsBuilder = [NSMutableString string];
    [motorcycleDetailsBuilder appendString:@"\n\nMotorcycle-Specific Details:\n\n"];
    
    //Add info about motorcycle-specific features.
    [motorcycleDetailsBuilder appendFormat:@"Engine Noise: %@", self.engineNoise];
    
    //Create the final string by combining basic and motorcycle-specific details.
    NSString *motorcycleDetails = [basicDetails stringByAppendingString:motorcycleDetailsBuilder];
    
    return motorcycleDetails;
}

Now, it’s time to create some instances of Motorcycle.

Open VehicleListTableViewController.m and make sure it can see the Motorcycle class by adding the following import:

#import "Motorcycle.h"

Next, find setupVehicleArray and add the following code below the Cars you’ve already added but above where you sort the array:

    //Create a motorcycle
    Motorcycle *harley = [[Motorcycle alloc] init];
    harley.brandName = @"Harley-Davidson";
    harley.modelName = @"Softail";
    harley.modelYear = 1979;
    harley.engineNoise = @"Vrrrrrrrroooooooooom!";
    
    //Add it to the array.
    [self.vehicles addObject:harley];
    
    //Create another motorcycle
    Motorcycle *kawasaki = [[Motorcycle alloc] init];
    kawasaki.brandName = @"Kawasaki";
    kawasaki.modelName = @"Ninja";
    kawasaki.modelYear = 2005;
    kawasaki.engineNoise = @"Neeeeeeeeeeeeeeeeow!";
    
    //Add it to the array
    [self.vehicles addObject:kawasaki];

The above code simply instantiates two Motorcycle objects and adds them to the vehicles array.

Build and run your application; you’ll see the two Motorcycle objects you added in the list:

Added Motorcycles

Tap on one of them, and you'll be taken to the details for that Motorcycle, as shown below:

Motorcycle Details

Whether it's a car or a motorcycle (or even a plain old vehicle), you can call vehicleDetailsString and get the relevant details.

By using proper separation between the model, the view, and the view controller and inheritance, you’re now able to display data for several subclasses of the same superclass without having to write tons of extra code to handle different subclass types. Less code written == happier developers! :]

Housing Logic in the Model Class

You can also use this approach to keep some of the more complicated logic encapsulated within the model class. Think about a Truck object: many different types of vehicles are referred to as a “truck”, ranging from pickup trucks to tractor-trailers. Your truck class will have a little bit of logic to change the truck's behavior based on the number of cubic feet of cargo it can haul.

Go to File\New\File\CocoaTouch\Objective-C Class, and create a new subclass of Vehicle called Truck.

Add the following integer property to Truck.h to hold the truck's capacity data:

@property (nonatomic, assign) NSInteger cargoCapacityCubicFeet;

Since there are so many different types of trucks, you don't need to create an init method that provides any of those details automatically. You can simply override the superclass methods which would be the same no matter the size of the truck.

Open Truck.m and add the following methods:

#pragma mark - Superclass overrides
- (NSString *)goForward
{
    return [NSString stringWithFormat:@"%@ Depress gas pedal.", [self changeGears:@"Drive"]];
}

- (NSString *)stopMoving
{
    return [NSString stringWithFormat:@"Depress brake pedal. %@", [self changeGears:@"Park"]];
}

Next, you’ll want to override a couple of methods so that the string returned is dependent on the amount of cargo the truck can haul. A big truck needs to sound a backup alarm, so you can create a private method (i.e., a method not declared in the .h file and thus not available to other classes) to do this.

Add the following helper method code to Truck.m:

#pragma mark - Private methods
- (NSString *)soundBackupAlarm
{
    return @"Beep! Beep! Beep! Beep!";
}

Then back in the superclass overrides section, you can use that soundBackupAlarm method to create a goBackward method that sounds the alarm for big trucks:

- (NSString *)goBackward
{
    NSMutableString *backwardString = [NSMutableString string];
    if (self.cargoCapacityCubicFeet > 100) {
        //Sound a backup alarm first
        [backwardString appendFormat:@"Wait for \"%@\", then %@", [self soundBackupAlarm], [self changeGears:@"Reverse"]];
    } else {
        [backwardString appendFormat:@"%@ Depress gas pedal.", [self changeGears:@"Reverse"]];
    }
    
    return backwardString;
}


Trucks also have very different horns; a pickup truck, for instance, would have a horn similar to that of a car, while progressively larger trucks have progressively louder horns. You can handle this with some simple if/else statements in the makeNoise method.

Add makeNoise as follows:

- (NSString *)makeNoise
{
    if (self.numberOfWheels <= 4) {
        return @"Beep beep!";
    } else if (self.numberOfWheels > 4 && self.numberOfWheels <= 8) {
        return @"Honk!";
    } else {
        return @"HOOOOOOOOONK!";
    }
}

Finally, you can override the vehicleDetailsString method in order to get the appropriate details for your Truck object. Add the method as follows:

-(NSString *)vehicleDetailsString
{
    //Get basic details from superclass
    NSString *basicDetails = [super vehicleDetailsString];
    
    //Initialize mutable string
    NSMutableString *truckDetailsBuilder = [NSMutableString string];
    [truckDetailsBuilder appendString:@"\n\nTruck-Specific Details:\n\n"];
    
    //Add info about truck-specific features.
    [truckDetailsBuilder appendFormat:@"Cargo Capacity: %d cubic feet", self.cargoCapacityCubicFeet];
    
    //Create the final string by combining basic and truck-specific details.
    NSString *truckDetails = [basicDetails stringByAppendingString:truckDetailsBuilder];
    
    return truckDetails;    
} 

Now that you’ve got your Truck object set up, you can create a few instances of it. Head back to VehicleListTableViewController.m and add the following import to the top of the file so that it knows about the existence of the Truck class:

#import "Truck.h"

Find the setupVehicleArray method and add the following code right above where you sort the array:

    //Create a truck
    Truck *silverado = [[Truck alloc] init];
    silverado.brandName = @"Chevrolet";
    silverado.modelName = @"Silverado";
    silverado.modelYear = 2011;
    silverado.numberOfWheels = 4;
    silverado.cargoCapacityCubicFeet = 53;
    silverado.powerSource = @"gas engine";
    
    //Add it to the array
    [self.vehicles addObject:silverado];
    
    //Create another truck
    Truck *eighteenWheeler = [[Truck alloc] init];
    eighteenWheeler.brandName = @"Peterbilt";
    eighteenWheeler.modelName = @"579";
    eighteenWheeler.modelYear = 2013;
    eighteenWheeler.numberOfWheels = 18;
    eighteenWheeler.cargoCapacityCubicFeet = 408;
    eighteenWheeler.powerSource = @"diesel engine";
    
    //Add it to the array
    [self.vehicles addObject:eighteenWheeler];

This will create a couple of Truck objects with the truck-specific properties to join the cars and motorcycles in the array.

Build and run you application; select one of the Trucks to verify that you can now see the appropriate Truck-specific details, as demonstrated below:

Truck-specific Details

That looks pretty great! The truck details are coming through thanks to the vehicleDetailsString method, inheritance, and overridden implementations.

Where To Go From Here?

You can download a copy of the project up to this point.

You've set up a base Vehicle class with Car, Motorcycle, and Truck subclasses, all listed in the same table view. However, there’s no way to verify that all of your truck's size-specific handling is working correctly.

Part 2 of this tutorial will fill out the rest of the app to display more vehicle details. Along the way, you'll learn about polymorphism and a couple of additional major design patterns for Object-Oriented Programming.

Until then, why not try implementing a Bicycle class or a few more custom properties to some of the vehicle subclasses? Or you can read up on Apple's reference guide Object-Oriented Programming with Objective-C.

Got questions? Ask away in the comments below!

Ellen Shapiro

Ellen Shapiro is the Lead Mobile Developer for SpotHero in Chicago, Illinois. She is working in her spare time to help bring Hum to life. She’s also developed several independent applications through her personal company, Designated Nerd Software.

When she's not writing code, she's usually tweeting about it.

Other Items of Interest

Save time.
Learn more with our video courses.

raywenderlich.com Weekly

Sign up to receive the latest tutorials from raywenderlich.com each week, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

PragmaConf 2016 Come check out Alt U

Our Books

Our Team

Video Team

... 20 total!

Swift Team

... 15 total!

iOS Team

... 43 total!

Android Team

... 14 total!

macOS Team

... 11 total!

Unity Team

... 11 total!

Articles Team

... 12 total!

Resident Authors Team

... 15 total!