Intro to Object-Oriented Design in Swift: Part 1/2
Learn the basics of object-oriented design in Swift. In this first part, you’ll learn about objects and classes, inheritance, and the Model-View-Controller relationship. By Ray Fix.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Intro to Object-Oriented Design in Swift: Part 1/2
35 mins
- Getting Started
- The Basics Of Objects
- Minor Digression: Initialization and Properties
- Minor Digression: Class versus Struct
- Describing the Object
- A Minor Digression: Class Methods vs. Instance Methods
- Adding Basic Methods to Your Class
- Inheritance
- Overriding Methods
- Building out the User Interface
- Hooking Your Data to Your View
- Model-View-Controller Encapsulation of Logic
- Creating Subclasses via Inheritance
- Housing Logic in the Model Class
- Where To Go From Here?
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:
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:
Tap on one of them, and you’ll be taken to the details for that Motorcycle
, as shown below:
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:
That looks pretty great! The truck details are coming through thanks to the vehicleDetails
computed property, inheritance, and overridden implementations.