Swift Design Patterns: 3 Essential Patterns Explored

Welcome to the world of Swift development, where efficient and clean code is paramount. Swift Design Patterns are the backbone of a well-structured application, as they offer reusable solutions to common programming challenges. In this comprehensive guide, we will delve into three Essential Patterns that every Swift developer should master. By understanding and implementing these patterns, you will not only elevate your coding expertise but also create maintainable and scalable applications. So, let’s dive in and explore these game-changing patterns together.

Heads up: If you don’t have a lot of experience working with Swift I recommend that you familiarize yourself with it.

What are Design Patterns?

As the name implies, a design pattern is nothing more than a repeatable pattern that helps you create better code. It’s just that simple.

I know. You probably expected a more convoluted definition. But that’s really what it boils down to.

Much like a template, a design pattern serves as a guide for programming that has been proven to be effective.

But isn’t that the same thing?

Well, no.

Unlike templates, code snippets, or project scaffolding, you can’t merely paste a design pattern into your application. This is because a design pattern isn’t a piece of code. Instead, it’s a broad concept as to how to solve a problem or meet a requirement.

Why are Design Patterns Important?

Among the many benefits of design patterns, you can find the following:

  • Consolidation of code: Design patterns help minimize the amount of work and, by extension, the amount of code necessary. It achieves this by providing a proven structure that meets extensive requirements.
  • Standardization of process. Design patterns provide a standard baseline that helps developers understand the thought process and approach to a problem preceding a solution by simply stating what pattern was used.
  • Tested framework. Design patterns provide tested and optimized solutions, sparing you from common architectural design and development mistakes.

Types of Design Patterns in Swift

Design patterns can be classified into three distinctive types. Creational, Structural, and Behavioral.

Let’s explore what they are.

Creational design patterns

Patterns falling into the creational type revolve around mechanisms for object creation.

These patterns focus on the particular situation for which you create objects.

Some examples of this design pattern are the singleton design pattern, prototype design pattern, and builder design pattern.

Structural design patterns

In contrast, the goal of structural design patterns is to streamline the design of classes and structures.

These patterns focus on finding a straightforward method for establishing relationships between objects and classes.

Some examples of this design pattern are the adapter design pattern, the bridge design pattern, and the proxy design pattern.

Behavioral design patterns

Finally, behavioral design patterns determine common communication patterns between entities and apply them.

Some examples of this design pattern are the observer design pattern, the template design pattern, and the chain of responsibility design pattern.

Top 3 Swift Design Patterns

As you can see, some of these design patterns are common in the development world. And for a good reason.

The chances are that you were taught some of them, or you internalized them by merely working and collaborating with other people. This is because they serve as a solid foundation for robust development and good practices that deliver reliable results.

Now, let’s see the top three must-know design patterns for Swift.

Builder Design Pattern

The Builder design pattern allows for creating complex objects one step at a time.

Instead of creating monolithic objects with numerous parameters, dependencies, and configurations, builders reduce the scope to specific requirements so that an object can be more manageable and straightforward for different uses. This pattern saves a significant amount of time and effort when dealing with extensive libraries and large codebases containing objects that satisfy many requirements and features.

Let’s illustrate.

An excellent example of this pattern is an object which requires gradual initialization of numerous properties and needs many nested objects.

Generally, the initialization code lies under a constructor with many parameters or is spread all over the class.

To solve this issue, the builder design pattern requires developers instead to isolate the object constructor in the class code.

This constructor is then assigned to what is known as builders and split into multiple steps. Now, to instantiate the object, all that is needed is to call the necessary builders to satisfy the requirements.

The builder design pattern is handy when composing complex objects is inevitable and when code must construct different views for specific objects.

Here’s a simple example of code following the builder design pattern.

protocol CarShop {
    func buildCar()
}

class Toyota: CarShop {
    func buildCar() {
        print("New Toyota Built!")
    }
}

class Honda: CarShop {
    func buildCar() {
        print("New Honda Built!")
    }
}

class Builder {
    let carShop: CarShop
    
    init(carShop: CarShop) {
        self.carShop = carShop
    }
    
    func build() {
        carShop.buildCar()
    }
}

let toyotaBuilder = Builder(carShop: Toyota())
toyotaBuilder.build()
toyotaBuilder.build()
toyotaBuilder.build()

let hondaBuilder = Builder(carShop: Honda())
hondaBuilder.build()
hondaBuilder.build()

Notice that both the Toyota and Honda shop classes follow the ‘CarShop’ protocol, which defines the ‘buildCar’ function for the builder class. So by using the builder object and providing it with the ‘carShop’ it can then build cars.

In this case, if you need to add more complexity to either the Toyota or Honda, the constructor for these classes can stay isolated on the builder.

Adapter Design Pattern

The adapter design pattern allows objects that are otherwise incompatible to work together by serving as an intermediary, adapting it to the other object.

The role of an adapter is to wrap an object and create a mechanism to translate or adapt its properties and behavior to the target object.

To illustrate, an object that contains a currency like the dollar can be wrapped into an adapter that converts it into yen for an API that only works with that currency.

The adapter design pattern is handy when using a third-party class containing an incompatible interface and when using existing subclasses with some missing functionality.

import EventKit

protocol EventProtocol: AnyObject {
    var title: String { get }
    var from: String { get }
    var to: String { get }
}

class MyEventAdapter: EventProtocol {
    private var event: EKEvent
    
    private lazy var dateFormatter: DateFormatter = {
        let df = DateFormatter()
        df.dateFormat = "MM-dd-yyyy HH:mm"
        
        return df
    }()
    
    var title: String {
        return event.title
    }
    
    var from: String {
        return dateFormatter.string(from: event.startDate)
    }
    
    var to: String {
        return dateFormatter.string(from: event.endDate)
    }
    
    init(event: EKEvent) {
        self.event = event
    }
}

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy/MM/dd HH:mm"

let eventStore = EKEventStore()
let event = EKEvent(eventStore: eventStore)

event.title = "Design Pattern Meetup"
event.startDate = dateFormatter.date(from: "2022/01/01 15:00")
event.endDate = dateFormatter.date(from: "2022/12/12 18:00")

let myAdapter = MyEventAdapter(event: event)
myAdapter.title
myAdapter.from
myAdapter.to

Notice how the protocol establishes the same properties between the ‘EKEvent’ and the ‘MyEventAdapter’ class, and this class then maps the properties to match the protocol.

Template Design Pattern

The template design pattern allows subclasses to redefine specific elements of an algorithm without changing its general structure.

In essence, this design pattern defines a structure for algorithms and delegates responsibility to subclasses. It achieves this by sectioning an algorithm into a series of stages, representing them in different methods, and calling them sequentially.

The template design pattern is handy when subclasses must extend an algorithm without modifying its base structure and when having multiple classes that are responsible for comparable actions.

Let’s code it

Here’s an example of the template pattern with the standard photo and camera permission snippet.

import AVFoundation
import Photos

typealias AuthorizationCompletion = (status: Bool, message: String)

class PermissionService: NSObject {
    private var message: String = ""
    
    func authorize(_ completion: @escaping (AuthorizationCompletion) -> Void) {
        let status = checkStatus()
        
        guard !status else {
            complete(with: status, completion)
            
            return
        }
        
        requestAuthorization { [weak self] status in
            self?.complete(with: status, completion)
        }
    }

    func checkStatus() -> Bool {
        return false
    }
    
    func requestAuthorization(_ completion: @escaping (Bool) -> Void) {
        completion(false)
    }
    
    func formMessage(with status: Bool) {
        let messagePrefix = status ? "You have access to " : "You haven't access to "
        let nameOfCurrentPermissionService = String(describing: type(of: self))
        let nameOfBasePermissionService = String(describing: type(of: PermissionService.self))
        let messageSuffix = nameOfCurrentPermissionService.components(separatedBy: nameOfBasePermissionService).first!
        
        message = messagePrefix + messageSuffix
    }
    
    private func complete(with status: Bool, _ completion: @escaping (AuthorizationCompletion) -> Void) {
        formMessage(with: status)
        
        let result: AuthorizationCompletion = (status: status, message: message)
        
        completion(result)
    }
}

class CameraPermissionService: PermissionService {
    override func checkStatus() -> Bool {
        let status = AVCaptureDevice.authorizationStatus(for: .video).rawValue
        
        return status == AVAuthorizationStatus.authorized.rawValue
    }
    
    override func requestAuthorization(_ completion: @escaping (Bool) -> Void) {
        AVCaptureDevice.requestAccess(for: .video) { status in
            completion(status)
        }
    }
}

class PhotoPermissionService: PermissionService {
    override func checkStatus() -> Bool {
        let status = PHPhotoLibrary.authorizationStatus().rawValue
        
        return status == PHAuthorizationStatus.authorized.rawValue
    }
    
    override func requestAuthorization(_ completion: @escaping (Bool) -> Void) {
        PHPhotoLibrary.requestAuthorization { status in
            completion(status.rawValue == PHAuthorizationStatus.authorized.rawValue)
        }
    }
}

let permissionServices = [CameraPermissionService(), PhotoPermissionService()]

for permissionService in permissionServices {
    permissionService.authorize { (_, message) in
        print(message)
    }
}

In this case, the mechanism to get permission to access the photos and camera is handled by two subclasses, CameraPermissionService and PhotoPermissionService. These classes redefine specific stages of the algorithm while maintaining the rest intact.

Moving On

The great thing about design patterns, and Swift design patterns in specific, is that they exist to make it easier for you, the programmer, to write your code effectively. In addition, they help you create a simple and proven developing environment that saves you tons of time and headaches. All while providing high-quality results.

However, it’s essential to know that no code is infallible to bugs and human error, even when using design patterns. If you want to make sure that your code is robust and reliable, check out this post on how to properly design and develop your testing workflow.

This post was originally written for and published by Waldo.com


Posted

in

by

Tags:

Comments

One response to “Swift Design Patterns: 3 Essential Patterns Explored”

  1. […] that we have a basic understanding of WebSockets let’s look at how to use them in an iOS application so we can master WebSockets in iOS. For this, we will use the SocketIO library, which provides a […]

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.