Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
2/2
Ellen Shapiro on November 15, 2013
Polymorphism
The general definition of Polymorphism comes from its Greek
roots Poly means many, and Morph means forms.
subclass or alter the original class. Theyre primarily used for adding methods to the stock UIKit components that
come with iOS.
The difference between a category and a subclass is pretty simple: A category allows you to add new methods, but
not override existing methods. You cant add new properties or instance variables to categories you can only use
existing ones. If you want to add a new property or instance variable, youll need to create a subclass and use the
power of inheritance to create your additional properties and methods.
But what if you dont need to do that? What if you just want to create a simple way to encapsulate something you
have to do repeatedly with a particular UIKit object? In this case, a category is the perfect solution.
In your tutorial app, youre going to add a convenience method to UIAlertView to do away with performing the
alloc-init-showdance for simple alerts over and over again in your code.
Once youve created the files, you can see by their filenames the syntax Xcode uses to indicate that a file is a
category, as shown below:
The [Component]+[Category Name] format indicates both the original class being decorated and what the category
itself does. Its completely acceptable to use multiple categories on the same class in the same application; this
makes it easier to reuse categories in other applications.
Creating a method on a category is very similar to creating a method on a normal class. Since youre going to be
creating a new instance of UIAlertView rather than working with an existing instance, open up
UIAlertView+Convenience.h and add the following class declaration for your method after the @interfaceline:
// Shows a UIAlertView with the given title and message, and an OK button to dismiss
it.
+ (void)showSimpleAlertWithTitle:(NSString *)title andMessage:(NSString *)message;
Then, open UIAlertView+Convenience.m and add the method implementation:
+ (void)showSimpleAlertWithTitle:(NSString *)title andMessage:(NSString *)message
{
UIAlertView *simpleAlert = [[UIAlertView alloc] initWithTitle:title
message:message
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[simpleAlert show];
}
Youre not doing anything revolutionary here youre just combining a bunch of the boilerplate code youd to write
over and over and over to show a simple alert view with a single button to dismiss it.
Next, open VehicleDetailViewController.m and add the following import:
#import "UIAlertView+Convenience.h"
Towards the bottom of the file, youll find several IBActionmethods with just TODOcomments in the method body.
Update goForward, goBackward, stopMoving, and makeNoiseas shown below to use your new category:
-(IBAction)goForward
{
[UIAlertView showSimpleAlertWithTitle:@"Go Forward" andMessage:[self.detailVehicle
goForward]];
}
-(IBAction)goBackward
{
[UIAlertView showSimpleAlertWithTitle:@"Go Backward" andMessage:[self.detailVehicle
goBackward]];
}
-(IBAction)stopMoving
{
[UIAlertView showSimpleAlertWithTitle:@"Stop Moving" andMessage:[self.detailVehicle
stopMoving]];
}
-(IBAction)makeNoise
{
[UIAlertView showSimpleAlertWithTitle:@"Make Some Noise!" andMessage:
[self.detailVehicle makeNoise]];
}
Build and run your application; after selecting a vehicle, press any button except the Turn button, and youll see
the appropriate message for each instance of a Vehicle. For example, if you press the Make Some Noise! button
for various Vehicles, youll see the following:
But what if you need to do something a bit more complicated something that requires getting information from the
UIAlertView that youve shown? This is where the Adapter pattern and its use of delegation comes in handy.
In order to help figure out how many degrees the user wants to turn a vehicle, youll take advantage of the
UIAlertViewDelegate protocol to get the information the user enters into a UIAlertView.
Note: Implementing a specific protocol in a class is frequently called conforming to that protocol.
Youll use all this to implement a mechanism that figures out how many degrees the user wants to turn their
Vehicle.
Open VehicleDetailViewController.m and replace turnwith the following implementation:
-(IBAction)turn
{
//Create an alert view with a single text input to capture the number of degrees
//to turn your vehicle. Set this class as the delegate so one of the delegate
methods
//can retrieve what the user entered.
UIAlertView *turnEntryAlertView = [[UIAlertView alloc] initWithTitle:@"Turn"
message:@"Enter number of degrees to turn:" delegate:self cancelButtonTitle:@"Cancel"
otherButtonTitles:@"Go!", nil];
turnEntryAlertView.alertViewStyle = UIAlertViewStylePlainTextInput;
[[turnEntryAlertView textFieldAtIndex:0] setKeyboardType:UIKeyboardTypeNumberPad];
[turnEntryAlertView show];
}
The method creates a UIAlertView with a text input that will prompt the user for a numeric value.
Next, youll need to add a delegate method for the UIAlertView instance to call back after the user enters a number.
Add the following method:
#pragma mark - UIAlertViewDelegate method
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
//Note: Only one alert view will actually declare this class its delegate, so we
can
//
proceed without double-checking the alert view instance. If you have more
than
//
one alert view using the same class as its delegate, make sure you check
which
//
UIAlertView object is calling this delegate method.
if (buttonIndex != alertView.cancelButtonIndex) {
//Get the text the user input in the text field
NSString *degrees = [[alertView textFieldAtIndex:0] text];
//Convert it from a string to an integer
NSInteger degreesInt = [degrees integerValue];
//Use the simple alert view to display the information for turning.
[UIAlertView showSimpleAlertWithTitle:@"Turn" andMessage:[self.detailVehicle
turn:degreesInt]];
} //else the user has cancelled, and we don't need to do anything.
}
The above code implements a selected UIAlertViewDelegate method so you can detect when a button is clicked.
Build and run your project; select a Vehicle from the list, tap the Turn button and enter a number of degrees to turn,
like so:
If you hit Cancel, nothing will happen since youve set your delegate to ignore that index. However, if you hit Go!, the
first UIAlertView disappears and the following UIAlertView will appear:
Your application is now functionally complete. However, what if you want to make your code a little more elegant so
its easier to maintain and add to it later? Its time to learn about two more object-oriented design patterns to make
your coding life easier!
One other thing to note with factory methods and inheritance: since they return a fully instantiated object, you have to
be careful about how you use them in superclasses, as they return a particular class of object.
Go to Vehicle.m and add the following implementation of the factory method:
#pragma mark - Factory method
+ (instancetype)vehicleWithBrandName:(NSString *)brandName modelName:(NSString
*)modelName modelYear:(NSInteger)modelYear powerSource:(NSString *)powerSource wheels:
(NSInteger)numberOfWheels;
{
//Use self in the superclass to ensure you're getting the proper return type for
each of the subclasses.
Vehicle *newVehicle = [[self alloc] init];
//Set the provided values to the appropriate instance variables.
newVehicle.brandName = brandName;
newVehicle.modelName = modelName;
newVehicle.modelYear = modelYear;
newVehicle.powerSource = powerSource;
newVehicle.numberOfWheels = numberOfWheels;
Note: Quality Coding has a great article How to Botch Your Objective-C Factory Method, that goes into a lot
more depth on this subject.
[self.vehicles addObject:mustang];
//Create another car.
Car *outback = [Car carWithBrandName:@"Subaru" modelName:@"Outback" modelYear:1999
powerSource:@"gas engine" numberOfDoors:5 convertible:NO hatchback:YES
sunroof:NO];
//Add it to the array.
[self.vehicles addObject:outback];
//Create another car
Car *prius = [Car carWithBrandName:@"Toyota" modelName:@"Prius" modelYear:2007
powerSource:@"hybrid engine" numberOfDoors:5 convertible:YES hatchback:YES
sunroof:YES];
//Add it to the array.
[self.vehicles addObject:prius];
Build and run your application; everything looks the same as it did before, but you know that underneath the hood
youre using far less code to creating your Vehicle array. You can now take this same pattern and apply it to the
Motorcycle and Truck classes.
2. You cant accidentally overwrite the _vehicleListinstance. Since static variables can only be initialized once,
if someone accidentally adds another [[VehicleList alloc] init]call once the _vehicleListvariable
has an initialized object, it wont have any effect on your existing VehicleList object.
Next, you need to move your vehicle creation over from the VehicleListTableViewController to the VehicleList class.
First, import the Car, Motorcycle, and Truck classes at the top of VehicleList.m:
#import "Car.h"
#import "Motorcycle.h"
#import "Truck.h"
Next, add the following class method to VehicleList.m:
+ (NSArray *)initialVehicleList
{
//Initialize mutable array.
NSMutableArray *vehicles = [NSMutableArray array];
//Create a car.
Car *mustang = [Car carWithBrandName:@"Ford" modelName:@"Mustang" modelYear:1968
powerSource:@"gas engine" numberOfDoors:2 convertible:YES hatchback:NO
sunroof:NO];
//Add it to the array
[vehicles addObject:mustang];
//Create another car.
Car *outback = [Car carWithBrandName:@"Subaru" modelName:@"Outback" modelYear:1999
powerSource:@"gas engine" numberOfDoors:5 convertible:NO hatchback:YES
sunroof:NO];
//Add it to the array.
[vehicles addObject:outback];
//Create another car
Car *prius = [Car carWithBrandName:@"Toyota" modelName:@"Prius" modelYear:2007
powerSource:@"hybrid engine" numberOfDoors:5 convertible:YES hatchback:YES
sunroof:YES];
//Add it to the array.
[vehicles addObject:prius];
//Add a motorcycle
Motorcycle *harley = [Motorcycle motorcycleWithBrandName:@"Harley-Davidson"
modelName:@"Softail"
modelYear:1979 engineNoise:@"Vrrrrrrrroooooooooom!"];
//Add it to the array.
[vehicles addObject:harley];
//Add another motorcycle
Motorcycle *kawasaki = [Motorcycle motorcycleWithBrandName:@"Kawasaki"
modelName:@"Ninja"
modelYear:2005 engineNoise:@"Neeeeeeeeeeeeeeeeow!"];
//Add it to the array
[vehicles addObject:kawasaki];
//Create a truck
Truck *silverado = [Truck truckWithBrandName:@"Chevrolet" modelName:@"Silverado"
modelYear:2011
powerSource:@"gas engine" wheels:4 cargoCapacityCubicFeet:53];
[vehicles addObject:silverado];
//Create another truck
Truck *eighteenWheeler = [Truck truckWithBrandName:@"Peterbilt" modelName:@"579"
modelYear:2013
powerSource:@"diesel engine" wheels:18 cargoCapacityCubicFeet:408];
[vehicles addObject:eighteenWheeler];
//Sort the array by the model year
NSSortDescriptor *modelYear = [NSSortDescriptor sortDescriptorWithKey:@"modelYear"
ascending:YES];
[vehicles sortUsingDescriptors:@[modelYear]];
return vehicles;
}
The above method can be called at any time to either create or reset the initial list of vehicles.
Youll notice that most of this code has been moved over from VehicleListTableViewController, but now the
vehicles are added to the newly created local vehiclesarray instead of VehicleListTableViewControllers
self.vehicles.
Now you can go back to VehicleListTableViewController.m and remove three things that are no longer needed:
1. Delete the entire setupVehiclesArraymethod and the call to it in awakeFromNib.
2. Delete the vehiclesinstance variable and the call to initialize it in awakeFromNib.
3. Delete the #importsfor Car.h, Motorcycle.h, and Truck.h
Your private interface for VehicleListTableViewController and awakeFromNibimplementation should now look like
this:
@interface VehicleListTableViewController ()
@end
@implementation VehicleListTableViewController
#pragma mark - View Lifecycle
- (void)awakeFromNib
{
[super awakeFromNib];
//Set the title of the View Controller, which will display in the Navigation bar.
self.title = @"Vehicles";
}
Youll notice that Xcode shows you have three errors, since there are three places where you used the vehicles
property to feed the UITableViewDataSource and segue handling methods. Youll need to update these to use your
new singleton instead.
First, import the VehicleList class at the top of VehicleListTableViewController.m so you can access the singleton:
#import "VehicleList.h"
Then, find the three spots where Xcode indicates an error and update the code to use the VehicleList singletons
array of vehiclesinstead, as shown below:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:
(NSInteger)section
{
return [[VehicleList sharedInstance] vehicles].count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:
(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"
forIndexPath:indexPath];
Vehicle *vehicle = [[VehicleList sharedInstance] vehicles][indexPath.row];
cell.textLabel.text = [vehicle vehicleTitleString];
return cell;
}
#pragma mark - Segue handling
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:@"showDetail"]) {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
Vehicle *selectedVehicle = [[VehicleList sharedInstance] vehicles]
[indexPath.row];
[[segue destinationViewController] setDetailVehicle:selectedVehicle];
}
}
Build and run your application; youll see the same list as you did before, but you can sleep better knowing that the
code behind the app is clean, concise, and easily extensible.
With all the changes above, youll be able to easily add new Vehicles to this list in the future. For instance, if you
were to add a new UIViewController that allows the user to add their own Vehicle, youd only need to add it to the
singletons Vehicles array.
Or, if you wanted to allow the user to edit a Vehicle, you could make sure you sent all the data back without needing
to implement a delegatefor the VehicleListViewController.
Theres one tricky thing to watch out for with singletons: they will stay alive for the entire duration of your apps
lifecycle, therefore you dont want to load them down with too much data. They can be great for lightweight data
storage or to make objects accessible throughout your application.
If youre storing a lot of data in your app, youll want to look at something more robust like Core Data to handle the
data storage and retrieval of your objects.
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.