intro to object-oriented design_ part 2_2 - ray wenderlich

14
Wheels, engines, movement…alike, yet different. Intro To Object-Oriented Design: Part 2/2 This post is also available in: Chinese (Simplified) In Part 1 of this tutorial , you learned the basics of object- oriented design: objects, inheritance, and the model-view- controller pattern. You created the beginnings of a simple application called Vehicles to help you gain a better understanding of these concepts. Here in the second part, you’re going to learn about Polymorphism and a couple of other key patterns in Object- Oriented programming: The Class Factory Method and the Singleton Instance. If you completed the previous tutorial, great! You can either pick up where you left off with your previous project. However, if you want to jump right into things, you can download the completed project from Part 1 to get started. Polymorphism The general definition of Polymorphism comes from its Greek roots – “Poly” means many, and “Morph” means forms. The computer-science specific definition, pulled from the Free Online Dictionary of Computing , is: A variable that may refer to objects whose class is not known at compile time and which respond at run time according to the actual class of the object to which they refer. The combination of these definitions boils down to “the ability of an object to be more than one thing at a time.” Sounds like your average iOS developer, doesn’t it? ;] There are several subtypes of polymorphism that are used within Objective-C, but two key ones you’ll see often are the Decorator pattern and the Adapter pattern. The Decorator Pattern From Apple’s Cocoa Fundamentals guide’s section on Cocoa Design Patterns : The Decorator design pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality. As does subclassing, adaptation of the Decorator pattern allows you to incorporate new behavior without modifying existing code. Decorators wrap an object of the class whose behavior they extend. The primary example of the Decorator pattern in Objective-C is the use of Categories. Categories are a special kind of class in iOS that allow you to add additional methods to classes without having to Ellen Shapiro on November 15, 2013

Upload: mohit-pundir

Post on 01-Oct-2015

225 views

Category:

Documents


2 download

DESCRIPTION

Intro to Object-Oriented Design_ Part 2_2 - Ray Wenderlich

TRANSCRIPT

  • Wheels, engines, movementalike, yet

    different.

    Intro To Object-Oriented Design: Part

    2/2

    This post is also available in: Chinese (Simplified)

    In Part 1 of this tutorial, you learned the basics of object-

    oriented design: objects, inheritance, and the model-view-

    controller pattern. You created the beginnings of a simple

    application called Vehicles to help you gain a better

    understanding of these concepts.

    Here in the second part, youre going to learn about

    Polymorphism and a couple of other key patterns in Object-

    Oriented programming: The Class Factory Method and the

    Singleton Instance.

    If you completed the previous tutorial, great! You can either pick

    up where you left off with your previous project. However, if you

    want to jump right into things, you can download the

    completed project from Part 1 to get started.

    Polymorphism

    The general definition of Polymorphism comes from its Greek

    roots Poly means many, and Morph means forms.

    The computer-science specific definition, pulled from the Free

    Online Dictionary of Computing, is:

    A variab le that may refer to ob jects whose class is not known at compile time and which respond at run time

    according to the actual class of the ob ject to which they refer.

    The combination of these definitions boils down to the ability of an object to be more than one thing at a time.

    Sounds like your average iOS developer, doesnt it? ;]

    There are several subtypes of polymorphism that are used within Objective-C, but two key ones youll see often are

    the Decorator pattern and the Adapter pattern.

    The Decorator Pattern

    From Apples Cocoa Fundamentals guides section on Cocoa Design Patterns:

    The Decorator design pattern attaches additional responsib ilities to an object dynamically. Decorators provide a

    flexib le alternative to subclassing for extending functionality. As does subclassing, adaptation of the Decorator

    pattern allows you to incorporate new behavior without modifying existing code. Decorators wrap an object of the

    class whose behavior they extend.

    The primary example of the Decorator pattern in Objective-C is the use of Categories.

    Categories are a special kind of class in iOS that allow you to add additional methods to classes without having to

    Ellen Shapiro on November 15, 2013

  • 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-show dance for simple alerts over and over again in your code.

    Implementing the Decorator Pattern

    Go to File\New\File\Cocoa Touch, and select Objective-C Category:

    Add the category name as Convenience as a category on UIAlertView:

  • 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 @interface line:

    // 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 IBAction methods with just TODO comments in the method body.

    Update goForward, goBackward, stopMoving, and makeNoise as 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.

    The Adapter Pattern

    Again from the Cocoa Fundamentals Guide:

    The Adapter design pattern converts the interface of a class into another interface that clients expect. Adapter lets

    classes work together that couldnt otherwise because of incompatib le interfaces. It decouples the client from the

    class of the targeted object.

    Protocols are the primary example of the Adapter pattern in Objective-C. This designates a number of methods that

    can be implemented by any class. Theyre most often used for DataSource and Delegate methods, but can also

    be used to help two unrelated classes communicate with each other.

    The advantage of this pattern is that as long as a class declares that it conforms to the protocol, it really doesnt

    matter whether its a model, a view, or a controller. It simply needs to know what is happening in the other class, and

    will implement any required methods needed to know about this.

  • 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.

    Implementing the Adapter Pattern

    Open VehicleDetailViewController.h and declare that it conforms to the UIAlertViewDelegate protocol by adding

    that protocols name in angle brackets, as so:

    @interface VehicleDetailViewController : UIViewController

    If you were to translate the above line into English, it would read: This is a VehicleDetailViewController, which is a

    subclass of UIViewController and conforms to the UIAlertViewDelegate protocol. If a class conforms to multiple

    protocols, you can list them all in the angle brackets separated by commas.

    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 turn with 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!

    Additional Object-Oriented Patterns

    While there are a ton of great patterns you can use with Object-Oriented programming (in fact, Eli Ganem has written

    an entire tutorial full of them), there are two that are quite useful in your Vehicles apps context: the Class Factory

  • Method and the Singleton Instance.

    Both of these patterns are used extensively in iOS development, and understanding what they do under the hood will

    help you understand the design of the code youll encounter as an iOS developer.

    Class Factory Methods

    The concept of a Class Factory method is to return an instantiated object of a particular class with as much data pre-

    populated as possible. One of the obvious benefits of using a factory method rather than calling alloc/init and

    setting properties, is that your code can be much shorter.

    In this way, youre using one method to create an object and set all its properties rather than one method to create

    the object, then writing multiple lines to set all the properties. However there are also two less-obvious benefits to

    this technique.

    One, it forces anyone using your class to provide exactly the information you need in order to create a fully

    functioning instance of your object. As you created the various objects in this tutorial, you may have noticed that it

    was easy to miss a property or two. With a factory method, you are forced to be conscious of exactly what information

    you are and are not providing about the object you want to create.

    Two, and admittedly much less of a problem with ARC code, which is pretty much anything written since iOS 5, is

    that factory methods will return autoreleased objects, freeing the caller from having to release them later. You likely

    wont need to worry about this issue unless youre supporting old code, but its a good thing to be aware of.

    Implementing the Vehicle Class Factory Method

    Open Vehicle.h and declare the following class factory method which accepts parameters representing all of the

    basic properties of a vehicle:

    //Factory Method+ (instancetype)vehicleWithBrandName:(NSString *)brandName modelName:(NSString *)modelName modelYear:(NSInteger)modelYear powerSource:(NSString *)powerSource wheels:(NSInteger)numberOfWheels;

    The instancetype type is a slightly safer version of id. Whereas an id parameter or return type can accept any

    subclass of NSObject, instancetype as a method signature tells you that you are definitely receiving an instance

    of the class or subclass of the class where the instance is being initialized.

    Note: Read up on the details of instancetype over on NSHipster

    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;

  • //Return the newly created instance. return newVehicle;}

    The factory method here initializes the object and sets up the properties. Since Vehicle has subclasses, you need to

    make sure youre using [[self alloc] init] rather than [[Vehicle alloc] init]. That way, subclasses like

    Car can also use this inherited factory method to get Car objects back rather than Vehicle objects.

    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.

    Implementing the Car Class Factory Method

    Go to Car.h and declare the following factory method:

    //Factory Method+(Car *)carWithBrandName:(NSString *)brandName modelName:(NSString *)modelName modelYear:(NSInteger)modelYear powerSource:(NSString *)powerSource numberOfDoors:(NSInteger)numberOfDoors convertible:(BOOL)isConvertible hatchback:(BOOL)isHatchback sunroof:(BOOL)hasSunroof;

    Since you need all the information for the Vehicle other than the number of wheels, youve added parameters to your

    method for all the other Vehicle data along with the Car-specific properties.

    Go to Car.m and replace init with the following implementation of the factory method:

    #pragma mark - Factory Method+(Car *)carWithBrandName:(NSString *)brandName modelName:(NSString *)modelName modelYear:(NSInteger)modelYear powerSource:(NSString *)powerSource numberOfDoors:(NSInteger)numberOfDoors convertible:(BOOL)isConvertible hatchback:(BOOL)isHatchback sunroof:(BOOL)hasSunroof{ //Create the car object using the superclass factory method. Car *newCar = [Car vehicleWithBrandName:brandName modelName:modelName modelYear:modelYear powerSource:powerSource wheels:4]; //Set the car-specific properties using the passed-in variables. newCar.numberOfDoors = numberOfDoors; newCar.isConvertible = isConvertible; newCar.isHatchback = isHatchback; newCar.hasSunroof = hasSunroof; //Return the fully instantiated Car object. return newCar;}

    Note that as a general rule of thumb, you dont have to choose between an init method and a factory method;

    however, in this case youre not going to be using the init method directly and the factory method now does

    everything your custom init method used to do. It makes sense at this point to get rid of the old code since you

    wont be needing it anymore.

    Next, go into VehicleListTableViewController.m and update the setupVehicleArray method to use the new

    factory method on each of the Car objects youre creating, as follows:

    //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

  • [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.

    Implementing the Motorcycle Class Factory Method

    In Motorcycle.h, add the following new declaration of the factory method

    //Factory Method+(Motorcycle *)motorcycleWithBrandName:(NSString *)brandName modelName:(NSString *)modelName modelYear:(NSInteger)modelYear engineNoise:(NSString *)engineNoise;

    In this case, youre adding the specific parameters for creating a new instance of Motorcycles.

    Now open up Motorcycle.m and replace the init method with your factory methods implementation, as below:

    #pragma mark - Factory Method+(Motorcycle *)motorcycleWithBrandName:(NSString *)brandName modelName:(NSString *)modelName modelYear:(NSInteger)modelYear engineNoise:(NSString *)engineNoise{ //Create a new instance of the motorcycle with the basic properties by calling the Factory //method on the superclass. Motorcycle *newMotorcycle = [Motorcycle vehicleWithBrandName:brandName modelName:modelName modelYear:modelYear powerSource:@"gas engine" wheels:2]; //Set the Motorcycle-specific properties. newMotorcycle.engineNoise = engineNoise; return newMotorcycle;}

    Implementing the Truck Class Factory Method

    Open Truck.h and add the following factory method declaration:

    //Factory Method+(Truck *)truckWithBrandName:(NSString *)brandName modelName:(NSString *)modelName modelYear:(NSInteger)modelYear powerSource:(NSString *)powerSource wheels:(NSInteger)numberOfWheels cargoCapacityCubicFeet:(NSInteger)cargoCapacityCubicFeet;

    Just as before, youre including parameters that are specific to your new Vehicle instance in this case, Truck.

    Open Truck.m, and add the following factory method implementation (in this case, there isnt an existing init to

    replace):

  • #pragma mark - Factory Method+(Truck *)truckWithBrandName:(NSString *)brandName modelName:(NSString *)modelName modelYear:(NSInteger)modelYear powerSource:(NSString *)powerSource wheels:(NSInteger)numberOfWheels cargoCapacityCubicFeet:(NSInteger)cargoCapacityCubicFeet{ //Create a new instance using the superclass's factory method. Truck *newTruck = [Truck vehicleWithBrandName:brandName modelName:modelName modelYear:modelYear powerSource:powerSource wheels:numberOfWheels]; newTruck.cargoCapacityCubicFeet = cargoCapacityCubicFeet; //Return the newly created truck instance. return newTruck;}

    Now that youve created your factory methods for Motorcycle and Truck, head back to

    VehicleDetailsViewController.m and update your code to use these new factory methods as shown below:

    //Add a motorcycle Motorcycle *harley = [Motorcycle motorcycleWithBrandName:@"Harley-Davidson" modelName:@"Softail" modelYear:1979 engineNoise:@"Vrrrrrrrroooooooooom!"]; //Add it to the array. [self.vehicles addObject:harley]; //Add another motorcycle Motorcycle *kawasaki = [Motorcycle motorcycleWithBrandName:@"Kawasaki" modelName:@"Ninja" modelYear:2005 engineNoise:@"Neeeeeeeeeeeeeeeeow!"]; //Add it to the array [self.vehicles addObject:kawasaki]; //Create a truck Truck *silverado = [Truck truckWithBrandName:@"Chevrolet" modelName:@"Silverado" modelYear:2011 powerSource:@"gas engine" wheels:4 cargoCapacityCubicFeet:53]; [self.vehicles addObject:silverado]; //Create another truck Truck *eighteenWheeler = [Truck truckWithBrandName:@"Peterbilt" modelName:@"579" modelYear:2013 powerSource:@"diesel engine" wheels:18 cargoCapacityCubicFeet:408]; [self.vehicles addObject:eighteenWheeler];

    Build and run your application; again, everything works exactly the same on the surface. However, youve shortened

    and simplified your code under the hood and moved often-repeated code into reusable factory methods.

    Factory methods add a level of convenience and guard against the possibility of inadvertently leaving off a required

    property. Youll see them in common places such as NSStrings stringWithFormat: or UIButtons

    buttonWithType: and now youve added them to your own vehicle class and subclasses!

    The Singleton Pattern

    One very specific, very useful type of Class Factory method is the Singleton. This ensures that a particular instance

    of a class is only initialized once.

    This is great for items that need to only have a single instance for instance, the UIApplication singleton

    sharedApplication or for those classes that are expensive to initialize, or which store small amounts of data

    which need to be accessed and updated throughout your app.

    In the case of your Vehicles app, you can see theres one piece of data that might need to be accessed and updated

    all over the place: your list of Vehicles. The list also violates MVC rules by letting VehicleListTableViewController

    manage its creation and existence. By moving the list of vehicles into its own singleton class, you gain a lot of

  • flexibility for the future.

    Go to File\New\File\Objective-C Class and create a subclass of NSObject called VehicleList. Open VehicleList.h

    and add the following class method declaration for the singleton instance along with a property to store the array of

    vehicles under the @interface line:

    //The list of vehicles.@property (nonatomic, strong) NSArray *vehicles; //Singleton Instance+ (VehicleList *)sharedInstance;

    Next, open VehicleList.m and add the following implementation of the singleton factory method:

    + (VehicleList *)sharedInstance{ //Declare a static instance variable static VehicleList *_vehicleList = nil; //Create a token that facilitates only creating this item once. static dispatch_once_t onceToken; //Use Grand Central Dispatch to create a single instance and do any initial setup only once. dispatch_once(&onceToken, ^{ //These are only invoked the onceToken has never been used before. _vehicleList = [[VehicleList alloc] init]; _vehicleList.vehicles = [VehicleList initialVehicleList]; }); //Returns the shared instance variable. return _vehicleList;}

    Notice that youre declaring the _vehicleList instance variable and onceToken GCD token as static variables.

    This means that the variable exists for the entire lifetime of the program. This helps with creating a singleton in two

    ways:

    1. Instead of checking the null/not null status of the _vehicleList instance variable, GCD can more quickly test

    whether the onceToken has been executed or not in order to decide whether it needs to create the

    _vehicleList instance. Using GCD to perform this check is also thread-safe, since dispatch_once

    ensures that when its called from multiple threads, each thread will finish before the next one is allowed to try

    and create the instance.

    2. You cant accidentally overwrite the _vehicleList instance. Since static variables can only be initialized once,

    if someone accidentally adds another [[VehicleList alloc] init] call once the _vehicleList variable

    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 vehicles array 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 setupVehiclesArray method and the call to it in awakeFromNib.

    2. Delete the vehicles instance variable and the call to initialize it in awakeFromNib.

    3. Delete the #imports for Car.h, Motorcycle.h, and Truck.h

    Your private interface for VehicleListTableViewController and awakeFromNib implementation 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 vehicles instead, 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.

    Where To Go From Here?

    In one single app, youve created a clean, object-oriented application using basic objects, inheritance, MVC design,

    polymorphism, as well as singleton and factory methods. You can review the source code of the finished project

    here.

    For more information on singletons, you can read Mike Ashs definitive, incredibly detailed post on the Care and

    Feeding of Singletons.

    If youd like more information about Object-Oriented patterns, Eli Ganem wrote a great tutorial called iOS Design

    Patterns that reviews a bit of what youve learned here, then introduces you to several more advanced design

    patterns that you can use to construct even more elegant code.

    If youve got questions, please ask away in the comments below!

    designatednerd

    Ellen Shapiro is a mobile developer in Chicago, Illinois who builds iOS and Android

    apps for Vokal Interactive, and is working in her spare time to help bring 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 about it.