Escolar Documentos
Profissional Documentos
Cultura Documentos
1/2
Ellen Shapiro on November 14, 2013
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 havent worked with other languages extensively, and youre 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, youll 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:
But so is this:
Or this:
Or this:
In this part of the tutorial, youll 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 youll use to learn about ObjectOriented Programming.
Youll 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, its 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. Its the parent class to
almost all of the everyday Objective-C classes.
Note: There are some C structs like CGRect and CGSize that dont use NSObject as their parent class, since
structs dont 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 Apples documentation to learn more about
NSObject.
Now open Car.m add the following initialization method under the @implementationline:
- (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 initimplementation simply sets the number of wheels to 4.
Did you notice that you didnt have to do anything extra to access the numberOfWheelsproperty of the Vehicle parent
class? Thats because by inheriting from the Vehicle class, Car already knows about all of Vehicles 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
@interfaceline:
@property (nonatomic, assign) BOOL isConvertible;
@property (nonatomic, assign) BOOL isHatchback;
@property (nonatomic, assign) BOOL hasSunroof;
@property (nonatomic, assign) NSInteger numberOfDoors;
Overriding Methods
Now that youve 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] theres
some important stuff in there that needs to execute before your UIViewController subclass can do its work.
However, since most of the methods youre going to override in the Car class are returning nil, you can just create
your own implementations. Theres nothing useful in the superclasss implementation so theres 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 dont need to be started, but cars do! In this case, youre not publicly declaring
startsince it should only be called within the implementation.
Note: Even though a method is private and other objects and classes cant see it, that doesnt protect a
subclass from overriding the method. You really cant stop something from going wrong in this case, but you
should make notes in your apps documentation, just as Apple does.
- (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.
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 whats being
displayed isnt terribly useful. Take a look at whats 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, youre grabbing a UITableViewCell object then getting the Vehicle at the index in the self.vehiclesarray
matching the rowof the cell youre constructing. Next, you set that specific Vehicles descriptionstring as the text
for the cells textLabel.
The string produced by the descriptionmethod (which is inherited from NSObject) isnt very human-friendly. Youll
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 VehicleListTableViewControllers 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 youll see are the same elements visible in the storyboard, with
none of the details from the Vehicle you selected:
Note: Youll notice that several of the IBOutletsare set up in the VehicleDetailViewController.m instead of in
the .h file, as they normally would be.
If you have properties you dont want to be publicly available to other classes, you can always add them to your
.m file in the private implementation. This is the @interfacethats 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 @propertydeclared 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 wont be accessible to any unrelated
classes or to subclasses of your class.
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 youll notice that theres one minor problem with this:
VehicleDetailsViewController only knows about the properties of the main Vehicle superclass; it doesnt 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.
//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:
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 @interfaceline:
@property (nonatomic, strong) NSString *engineNoise;
Then, open Motorcycle.m. Add the following initmethod:
#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 thats electricpowered 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 nilotherwise:
Tap on one of them, and youll be taken to the details for that Motorcycle, as shown below:
Whether its a car or a motorcycle (or even a plain old vehicle), you can call vehicleDetailsStringand get the
relevant details.
By using proper separation between the model, the view, and the view controller and inheritance, youre 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! :]
}
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 makeNoisemethod.
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 vehicleDetailsStringmethod 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 youve 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 setupVehicleArraymethod 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:
That looks pretty great! The truck details are coming through thanks to the vehicleDetailsStringmethod,
inheritance, and overridden implementations.
designatednerd
Ellen Shapiro is a mob ile developer in Chicago, Illinois who b uilds iOS and Android
apps for Vokal Interactive, and is working in her spare time to help b ring Hum to life.
Shes also developed several independent applications through her personal
company, Designated Nerd Software.
When she's not writing code, she's usually tweeting ab out it.