Intro to Object-Oriented Design: Part 1/2
This tutorial series will teach you the basics of object-oriented design. In this first part: Inheritance, and the Model-View-Controller pattern. By Ellen Shapiro.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Intro to Object-Oriented Design: Part 1/2
40 mins
- Getting Started
- The Basics Of Objects
- A Minor Digression: Under The Hood With Properties
- Describing the Object
- A Minor Digression: Class Methods vs. Instance Methods
- Adding Basic Methods to Your Class
- Inheritance
- Overriding Methods
- Building out the User Interface
- Hooking Your Data to Your View
- Model-View-Controller Encapsulation of Logic
- Creating Subclasses via Inheritance
- Housing Logic in the Model Class
- Where To Go From Here?
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:
- Include a call to the
[super method]
to take advantage of everything happening higher up the inheritance chain, or - 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.
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:
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:
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:
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.
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.