Quantcast
Channel: Kodeco | High quality programming tutorials: iOS, Android, Swift, Kotlin, Unity, and more
Viewing all articles
Browse latest Browse all 4370

Intro to Object-Oriented Design in Swift: Part 1/2

$
0
0
Object-oriented design

Object-oriented programmers love vehicle hierarchies.

Update note: This tutorial was updated for iOS 8 and Swift by Ray Fix. Original post by Tutorial Team member Ellen Shapiro.

A huge piece of the programming puzzle in working with Cocoa, Objective-C and Swift 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 UIView and UIButton 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. 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 Swift and Xcode before reading on. If you need a refresher on the basics, check out the Quick Start Tutorial.

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

The file Vehicle.swift defines a class of objects named Vehicle.

Open Vehicle.swift and add the following code within the class braces:

var brandName = "null"
var modelName = "null"
var modelYear = 0
var powerSource = "null"
var numberOfWheels = 0

These property declarations describe the characteristics of the object you want to keep track of. For now you are just providing placeholder values so that the Swift compiler can infer the types as a String or Int. (Later, in Part 2, you will add a real initializer to get rid of these dummy, placeholder values. This is why you are not using Swift Optionals to represent them.)

Minor Digression: Initialization and Properties

One of the important safety features of Swift is how it enables and enforces proper initialization of objects. This means that it’s virtually impossible to use uninitialized memory. The above properties are called stored properties and must be initialized either by setting them explicitly as you have done, or by initializing them in an init() method. If you supply all of your stored properties with values, the compiler will won’t require you to explicitly set them in the init() method.

Minor Digression: Class versus Struct

In Swift, you can define an object-like thing with the keyword class or struct. In this tutorial, you will be using class to define objects because these allow you to build class hierarchies and override methods. Structs, while very useful for many things, are more static in nature and don’t allow for this. For more information on when to use a class versus a struct see The Swift Programming Language Book.

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 that don’t return anything. However, to make it a little easier to display what’s happening in your app, you’re going to use some methods that return String objects.

A Minor Digression: Class Methods vs. Instance Methods

You’ve probably noticed when writing code that some methods have the class keyword in front of them. These indicate that a method is a Class method as opposed to a normal 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 is an action that can be performed with that blueprint, but without creating a specific copy of the object from that blueprint. For example, UIColor has the class method blackColor() that returns a new instance of UIColor set to black.

An instance method, requires a specific copy of the object created using that blueprint to perform any action. For example, the String 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

Still in Vehicle.swift, add the following methods to your Vehicle class.

func goForward() -> String {
  return "null"
}
 
func goBackward() -> String {
  return "null"
}
 
func stopMoving() -> String {
  return "null"
}
 
func turn(degrees:Int) -> String {
  var normalizedDegrees = degrees
 
  //Since there are only 360 degrees in a circle, calculate what a single turn would be.
  let degreesInACircle = 360
 
  if (normalizedDegrees > degreesInACircle || normalizedDegrees < -degreesInACircle) {
    // The % operator returns the remainder after dividing.
    normalizedDegrees = normalizedDegrees % degreesInACircle
  }
 
  return String(format: "Turn %d degrees.", normalizedDegrees)
}
 
func changeGears(newGearName:String) -> String {
  return String(format: "Put %@ into %@ gear.",self.modelName, newGearName)
}
 
func makeNoise() -> String {
  return "null"
}

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.swift, and replace the implementation of application(_:didFinishLaunchingWithOptions:) with the following:

func application(application: UIApplication, 
                 didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
 
  var vehicle = Vehicle()
  // Test methods with implementations
  println("Vehicle turn: \(vehicle.turn(700))")
  var changeGearResult = vehicle.changeGears("Test")
  println("Vehicle change gears: \(changeGearResult)")
 
  // Test methods without implementations
  println("Vehicle make noise: \(vehicle.makeNoise())")
  println("Vehicle go forward: \(vehicle.goForward())")
  println("Vehicle go backward: \(vehicle.goBackward())")
  println("Vehicle stop moving: \(vehicle.stopMoving())")
 
  return true
}

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. Since you have put in placeholder values for most of the properties, you will see this in the debug console:

Debugger

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

To see inheritance in action, create a subclass of Vehicle called Car. Go to File\New\File… and select iOS\Source\Swift File. Name the file Car.

Now open Car.swift and add the following class definition for Car:

class Car : Vehicle { 
  override init() {
    super.init()
    numberOfWheels = 4
  }
}

Car derives from your Vehicle class and inherits all of its methods and properties. Note that it makes perfect sense to say, “A Car is-a Vehicle“. If you can naturally say, “Subclass is-a Superclass”, derivation usually makes sense.

Since you provided values for all of the properties in the superclass, there’s no need to explicitly set each in your subclass. You may only want to set certain properties and leave the others as their defaults. For Cars, we know that the numberOfWheels will be four. To express this you override the initializer, call the superclass initializer, and then customize the numberOfWheels property to four. The order is significant. If you called super.init() last, numberOfWheels would get reset back to 0. Fortunately, the Swift complier will not allow you to make this mistake.

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! In Car.swift, above your initializer, add:

var isConvertible:Bool = false
var isHatchback:Bool = false
var hasSunroof:Bool = false
var numberOfDoors:Int = 0

Now, any Car object can be customized with all properties from the Vehicle and Car classes.

Overriding Methods

Now that you’ve added the appropriate additional properties, you can 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 viewDidLoad and others. In Swift, when you override a method, you must use the override keyword. This communicates your intent to the compiler. If you accidentally misspell a method parameter, for example, the compiler can let you know that something is wrong. Also, if you didn’t know you were overriding a method, the compiler will also let you know.

Unlike an initializer method, when you override a normal 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 empty values, you can just create your own implementations. There’s nothing useful in the superclass’s implementation so there’s no need to call it.

Still in Car.swift add the following private helper method to simplify your superclass override:

// MARK: - Private method implementations
private func start() -> String {
  return String(format: "Start power source %@.", powerSource)
}

Some vehicles such as bicycles don’t need to be started, but cars do! Here you use the access control keyword private to express the fact that this method should not be used outside of this file.

Note: Although this tutorial does not cover access control in detail, using it is pretty simple. If you do not add any access control keywords, the class, method or property defaults to internal and can be used by any code in the same module. If you use private, access is limited to the file. If you use public, you can access from anywhere, including outside the module. This is useful if you are building a framework, to be used by other projects, for example.

Next, add the remaining superclass overrides:

// MARK: - Superclass Overrides
override func goForward() -> String {
  return String(format: "%@ %@ Then depress gas pedal.", start(), changeGears("Forward"))
}
 
override func goBackward() -> String {
  return String(format: "%@ %@ Check your rear view mirror. Then depress gas pedal.", start(), changeGears("Reverse"))
}
 
override func stopMoving() -> String {
  return String(format: "Depress brake pedal. %@", changeGears("Park"))
}
 
override func makeNoise() -> String {
  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

Open VehicleListTableViewController.swift and add the following method just after viewDidLoad:

// MARK: - Data setup
func setupVehicleArray() {
  // Clear the array. (Start from scratch.)
  vehicles.removeAll(keepCapacity: true)
 
  // Create a car.
  var mustang = Car()
  mustang.brandName = "Ford"
  mustang.modelName = "Mustang"
  mustang.modelYear = 1968
  mustang.isConvertible = true
  mustang.isHatchback = false
  mustang.hasSunroof = false
  mustang.numberOfDoors = 2
  mustang.powerSource = "gas engine"
 
  // Add it to the array
  vehicles.append(mustang)
 
  // Create another car.
  var outback = Car()
  outback.brandName = "Subaru"
  outback.modelName = "Outback"
  outback.modelYear = 1999
  outback.isConvertible = false
  outback.isHatchback = true
  outback.hasSunroof = false
  outback.numberOfDoors = 5
  outback.powerSource = "gas engine"
 
  // Add it to the array.
  vehicles.append(outback)
 
  // Create another car
  var prius = Car()
  prius.brandName = "Toyota"
  prius.modelName = "Prius"
  prius.modelYear = 2002
  prius.hasSunroof = true
  prius.isConvertible = false
  prius.isHatchback = true
  prius.numberOfDoors = 4
  prius.powerSource = "hybrid engine"
 
  // Add it to the array.
  vehicles.append(prius)
 
  // Sort the array by the model year
  vehicles.sort { $0.modelYear < $1.modelYear }
}

This simply adds a data setup method to construct your vehicle array. Each Car is created and customized based on the values set on its properties.

In viewDidLoad() add the following after its super.viewDidLoad():

setupVehicleArray()
title = "Vehicles"

The above method executes just after your view loads from the Storyboard. 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 like the following:
Car Build

Your table view is populated with three entries of “Vehicle.Car”. Vehicle is the name of the module (app, in this case) and Car is the name of the class.

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:):

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
  let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
 
  let vehicle = vehicles[indexPath.row] as Vehicle
  cell.textLabel?.text = "\(vehicle)"
  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 string representation as the text for the cell’s textLabel.

The default string produced method 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 to Vehicle.swift and add the following property below your other properties:

// MARK: - Computed  Properties
 
var vehicleTitle: String {
  return String(format:"%d %@ %@", modelYear, brandName, modelName)
}

Until now, you have been using stored properties to keep track of attributes associated with your objects. This is an example of a computed property. This is a read-only property that isn’t backed by a variable; instead, it runs the code inside braces and generates a fresh string every time.

Go back to VehicleListTableViewController.swift and update tableView(_:cellForRowAtIndexPath:) to use this new property on Vehicle as follows:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
  let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
 
  let vehicle = vehicles[indexPath.row] as Vehicle
  cell.textLabel?.text = vehicle.vehicleTitle
  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.swift; you’ll see that while the UI was constructed in the Storyboard and all the @IBOutlets are already hooked up to save you some time fighting with AutoLayout, none of the data is hooked up.

Hooking Your Data to Your View

To hook up the data, go to VehicleDetailViewController.swift and fill in the method configureView() to take advantage of the vehicle set by the segue as follows:

func configureView() {
  // Update the user interface for the detail item.
  if let vehicle = detailVehicle {
    title = vehicle.vehicleTitle
 
    var basicDetails = "Basic vehicle details:\n\n"
    basicDetails += "Brand name: \(vehicle.brandName)\n"
    basicDetails += "Model name: \(vehicle.modelName)\n"
    basicDetails += "Model year: \(vehicle.modelYear)\n"
    basicDetails += "Power source: \(vehicle.powerSource)\n"
    basicDetails += "# of wheels: \(vehicle.numberOfWheels)\n"
 
    detailDescriptionLabel?.text = basicDetails
  }
}

Build and run your application; select a vehicle from the table view 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
basicDetails += "\n\nCar-Specific Details:\n\n"
basicDetails += "Number of doors: \(vehicle.numberOfDoors)"  // ERROR

But you’ll notice that this does not work because the compiler doesn’t know what the property numberOfDoors is. That property is associated with the Car subclass and not part of the Vehicle base class.

There are a couple of ways you can fix this.

One way is to have the view controller attempt to downcast the vehicle to a Car type using the as? Car operation. If it succeeds, access the Car properties and display them.

Any time you catch yourself downcasting, 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. This way the view controllers doesn’t need to concern itself with the details of specific subclasses.

Creating Subclasses via Inheritance

First, go to Vehicle.swift and add a new computed property to get a details string:

var vehicleDetails:String {
  var details = "Basic vehicle details:\n\n"
  details += "Brand name: \(brandName)\n"
  details += "Model name: \(modelName)\n"
  details += "Model year: \(modelYear)\n"
  details += "Power source: \(powerSource)\n"
  details += "# of wheels: \(numberOfWheels)\n"
  return details
}

The method is similar to what you added to VehicleDetailViewController.swift, 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.swift and add the override the property vehicleDetails:

override var vehicleDetails:String {
  // Get basic details from superclass
  let basicDetails = super.vehicleDetails
 
  // Initialize mutable string
  var carDetailsBuilder = "\n\nCar-Specific Details:\n\n"
 
  // String helpers for booleans
  let yes = "Yes\n"
  let no = "No\n"
 
  // Add info about car-specific features.
  carDetailsBuilder += "Has sunroof: "
  carDetailsBuilder += hasSunroof ? yes : no
 
  carDetailsBuilder += "Is Hatchback: "
  carDetailsBuilder += isHatchback ? yes : no
 
  carDetailsBuilder += "Is Convertible: "
  carDetailsBuilder += isConvertible ? yes : no
 
  carDetailsBuilder += "Number of doors: \(numberOfDoors)"
 
  // Create the final string by combining basic and car-specific details.
  let carDetails = basicDetails + 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 go back to VehicleDetailViewController.swift and replace configureView() with the much simpler implementation below to display this string that you’ve created:

func configureView() {
  // Update the user interface for the detail item.
  if let vehicle = detailVehicle {
    title = vehicle.vehicleTitle
    detailDescriptionLabel?.text = vehicle.vehicleDetails
  }
}

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 VehicleDetailsViewController 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… and select iOS\Source\Swift File. Name the file Motorcycle. Inside Motorcycle.swift, create a new subclass of Vehicle called Motorcycle as shown below:

class Motorcycle : Vehicle {
 
}

Since motorcycles can have either a deep booming engine noise, or a high-pitched whine of an engine noise, each Motorcycle object you create should specify which type of noise it makes. Add the following property and method to your new class:

var engineNoise = ""
 
override init() {
  super.init()
  numberOfWheels = 2
  powerSource = "gas engine"
}

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 the superclass methods.

// MARK: - Superclass Overrides
override func goForward() -> String {
  return String(format: "%@ Open throttle.", changeGears("Forward"))
}
 
override func goBackward() -> String {
  return String(format: "%@ Walk %@ backwards using feet.", changeGears("Neutral"), modelName)
}
 
override func stopMoving() -> String {
  return "Squeeze brakes"
}
 
override func makeNoise() -> String {
  return self.engineNoise
}

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

override var vehicleDetails:String {
  //Get basic details from superclass
  let basicDetails = super.vehicleDetails
 
  //Initialize mutable string
  var motorcycleDetailsBuilder = "\n\nMotorcycle-Specific Details:\n\n"
 
  //Add info about motorcycle-specific features.
  motorcycleDetailsBuilder += "Engine Noise: \(engineNoise)"
 
  let motorcycleDetails = basicDetails + motorcycleDetailsBuilder
 
  return motorcycleDetails
}

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

Open VehicleListTableViewController.swift, find setupVehicleArray() and add the following code below the Cars you’ve already added but above where you sort the array:

// Create a motorcycle
var harley = Motorcycle()
harley.brandName = "Harley-Davidson"
harley.modelName = "Softail"
harley.modelYear = 1979
harley.engineNoise = "Vrrrrrrrroooooooooom!"
 
// Add it to the array.
vehicles.append(harley)
 
// Create another motorcycle
var kawasaki = Motorcycle()
kawasaki.brandName = "Kawasaki"
kawasaki.modelName = "Ninja"
kawasaki.modelYear = 2005
kawasaki.engineNoise = "Neeeeeeeeeeeeeeeeow!"
 
// Add it to the array
self.vehicles.append(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 vehicleDetails 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… and select iOS\Source\Swift File. Name the file Truck. Inside Truck.swift, create a new subclass of Vehicle called Truck as you did for Car and Motorcycle:

Add the following integer property to Truck.swift to hold the truck’s capacity data:

class Truck : Vehicle {
  var cargoCapacityCubicFeet: Int = 0
}

Since there are so many different types of trucks, you don’t need to create an initializer 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.

//MARK: - Superclass overrides
override func goForward() -> String {
  return String(format:"%@ Depress gas pedal.", changeGears("Drive"))
}
 
override func stopMoving() -> String {
  return String(format:"Depress brake pedal. %@", 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.

Add the following private method code to your Truck class:

// MARK: - Private methods
private func soundBackupAlarm() -> String {
  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:

override func goBackward() -> String {
  if cargoCapacityCubicFeet > 100 {
    // Sound a backup alarm first
    return String(format:"Wait for \"%@\", then %@", soundBackupAlarm(), changeGears("Reverse"))
  } else {
    return String(format:"%@ Depress gas pedal.", changeGears("Reverse"))
  }
}

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 a switch statement in the makeNoise() method.

Add makeNoise() as follows:

override func makeNoise() -> String {
  switch numberOfWheels {
  case Int.min...4:
    return "Beep beep!"
  case 5...8:
    return "Honk!"
  default:
    return "HOOOOOOOOONK!"
  }
}

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

override var vehicleDetails: String {
  // Get basic details from superclass
  let basicDetails = super.vehicleDetails
 
  // Initialize mutable string
  var truckDetailsBuilder = "\n\nTruck-Specific Details:\n\n"
 
  // Add info about truck-specific features.
  truckDetailsBuilder += "Cargo Capacity: \(cargoCapacityCubicFeet) cubic feet"
 
  // Create the final string by combining basic and truck-specific details.
  let truckDetails = basicDetails + 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.swift, find the setupVehicleArray() method and add the following code right above where you sort the array:

// Create a truck
var silverado = Truck()
silverado.brandName = "Chevrolet"
silverado.modelName = "Silverado"
silverado.modelYear = 2011
silverado.numberOfWheels = 4
silverado.cargoCapacityCubicFeet = 53
silverado.powerSource = "gas engine"
 
// Add it to the array
vehicles.append(silverado)
 
// Create another truck
var eighteenWheeler = Truck()
eighteenWheeler.brandName = "Peterbilt"
eighteenWheeler.modelName = "579"
eighteenWheeler.modelYear = 2013
eighteenWheeler.numberOfWheels = 18
eighteenWheeler.cargoCapacityCubicFeet = 408
eighteenWheeler.powerSource = "diesel engine"
 
// Add it to the array
vehicles.append(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 vehicleDetails computed property, 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, proper initialization, and a couple of additional major design patterns for Object-Oriented Programming.

Got questions? Ask in the comments below!

Intro to Object-Oriented Design in Swift: Part 1/2 is a post from: Ray Wenderlich

The post Intro to Object-Oriented Design in Swift: Part 1/2 appeared first on Ray Wenderlich.


Viewing all articles
Browse latest Browse all 4370

Trending Articles