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

Learn the basics of object-oriented design in Swift. In this second part, you’ll learn about polymorphism, initialization, and some common design patterns for dealing with objects. By Ray Fix.

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Additional Object-Oriented Patterns

There are many more Object-Oriented design patterns that you can use to improve your code. Let’s look at two more: the Adapter and the Singleton.

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 you’ll encounter as an iOS developer.

The Adapter Pattern (Protocols)

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 couldn’t otherwise because of incompatible interfaces. It decouples the client from the class of the targeted object.

Protocols are the primary example of the Adapter pattern in Swift. This designates a number of methods and properties that can be implemented by any class. They’re 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 doesn’t matter whether it’s 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 or properties needed to know about this.

To make it easier to println() a Vehicle object, you will create what is called a protocol extension. This is one example of the adapter pattern in action. Open up Vehicle.swift and at the end of the file add the following stand-alone code outside of the Vehicle definition:

// MARK: An extension to make Vehicle printable

extension Vehicle : Printable {
  var description:String {
    return vehicleTitle + "\n" + vehicleDetails
  }
}

This declares the Vehicle class as conforming to the Printable protocol defined by the Swift Standard Library. Printable only requires that your class return a string property description. With this addition you can now println(vehicle). In other words, you have adapted Vehicle to be used as a String. To test it out, open VehicleDetailsViewController.swift and add viewWillAppear() just below viewDidLoad():

override func viewWillAppear(animated: Bool) {
  super.viewWillAppear(animated)
  if let vehicle = detailVehicle {
    println(vehicle)
  }
}

Build and run your application. Notice that whenever you tap on a vehicle, the vehicle information is printed to the Xcode console:

VehicleDebugger

Before conforming to the Printable protocol, all println() could do was print the module and class name: "Vehicle.Car".

Bonus: Notice that Swift lets you conform to a protocol using an extension. This is called a Protocol Extension. It is a nice way to organizer your code. Here too, you are applying two patterns simultaneously again. The Adapter (via protocol conformance) and the Decorator (via extension).

The Singleton Pattern

One very specific, very useful initialization pattern 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 there’s one piece of data that might need to be accessed and updated all over the place: your list of Vehicles. The current 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... and select iOS\Source\Swift File and name the file VehicleList. Open the file and create the singleton:

class VehicleList {
  let vehicles: [Vehicle]
  
  static var sharedInstance = VehicleList()

  private init() {
    vehicles = []
  }
}

The class VehicleList has property variable called sharedInstance which is declared static so you don't need an instance to get it. By making the init() private, you force clients to not make their own and use the shared instance.

Right now, you are returning an immutable empty vehicles list that is not so useful. To fix that, replace the private init() that creates the list.

  private init() {
    // Create a car.
    let mustang = Car(brandName: "Ford", modelName: "Mustang", modelYear: 1968, powerSource: "gas engine", isConvertible: true, isHatchback: false, hasSunroof: false, numberOfDoors: 2)
    
    // Create another car.
    let outback = Car(brandName: "Subaru", modelName: "Outback", modelYear: 1999, powerSource: "gas engine", isConvertible: false, isHatchback: true, hasSunroof: false, numberOfDoors: 5)
    
    // Create another car.
    let prius = Car(brandName: "Toyota", modelName: "Prius", modelYear: 2002, powerSource: "hybrid engine", isConvertible: false, isHatchback: true, hasSunroof: true, numberOfDoors: 4)
    
    // Create a motorcycle.
    let harley = Motorcycle(brandName: "Harley-Davidson", modelName: "Softail", modelYear: 1979, engineNoise: "Vrrrrrrrroooooooooom!")
    
    // Create another motorcycle.
    let kawasaki = Motorcycle(brandName: "Kawasaki", modelName: "Ninja", modelYear: 2005, engineNoise: "Neeeeeeeeeeeeeeeeow!")
    
    // Create a truck.
    let silverado = Truck(brandName: "Chevrolet", modelName: "Silverado", modelYear: 2011, powerSource: "gas engine", numberOfWheels: 4, cargoCapacityInCubicFeet: 53)
    
    // Create another truck.
    let eighteenWheeler = Truck(brandName: "Peterbilt", modelName: "579", modelYear: 2013, powerSource: "diesel engine", numberOfWheels: 18, cargoCapacityInCubicFeet: 408)
    
    // Sort the array by the model year
    let v = [mustang, outback, prius, harley, kawasaki, silverado, eighteenWheeler]
    
    vehicles = v.sorted { $0.modelYear < $1.modelYear }
  }

Now anywhere you reference VehicleList.sharedInstance.vehicles in your app, the list will be initialized the first time, and simply returned on subsequent references. Under the hood, Swift uses the lib dispatch to ensure that initialization occurs exactly once, even if multiple threads access it asynchronously.

Now go back to VehicleListTableViewController.swift and remove the vehicles property as well as the entire setupVehiclesArray() method.

Modify your viewDidLoad() method by removing the call to setupVehicleArray() since you just removed the method.

You’ll 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. You’ll need to update these to use your new singleton instead.

Find the three spots where Xcode indicates an error and update the code to use the VehicleList singleton’s array of vehicles instead, as shown below:

  
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
  if segue.identifier == "showDetail" {
    if let indexPath = self.tableView.indexPathForSelectedRow() {
      let vehicle = VehicleList.sharedInstance.vehicles[indexPath.row]
      (segue.destinationViewController as! VehicleDetailViewController).detailVehicle = vehicle
    }
  }
} 

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  return VehicleList.sharedInstance.vehicles.count
}
  
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
  let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
  
  let vehicle = VehicleList.sharedInstance.vehicles[indexPath.row]
  cell.textLabel?.text = vehicle.vehicleTitle
  return cell
}

Build and run your application; you’ll 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, you’ll 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, you’d only need to add it to the singleton array.

There’s one tricky thing to watch out for with singletons: they will stay alive for the entire duration of your app’s lifecycle, therefore you don’t 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 you’re storing a lot of data in your app, you’ll want to look at something more robust like Core Data to handle the data storage and retrieval of your objects.

Finally, keep in mind that singletons, if overused, can lead to hard to reuse code. This is because they couple modules tightly together in the same way that global variables do. This goes to show that while patterns can be very useful in some contexts, they are not a silver bullet and can sometimes be misused.

Contributors

Ray Fix

Author

Over 300 content creators. Join our team.