In this article, we will cover iOS patterns. Some of them will be considered in details, another ones will be shortly described, and others won’t be included at all.
What is a design pattern? This is a reusable solution to common problems in app development. Patterns help developers write the clear and easy-to-use code.
Requirements: time and brain :)
All the patterns can be divided into three categories: creational, structural, and behavioral.
Singleton (Creational category)
Imagine a situation where you need only one object copy. For example, it could be a real object: a printer, server, or something that shouldn’t have multiple copies. iOS devices for example, have a UserDefaults
object that can be accessed through the standard
property. Here is its implementation example:
private override init(){}static let sharedInstance = EventManager() override func copy()-> Any { fatalError("You are not allowed to use copy method on singleton!")} override func mutableCopy()-> Any { fatalError("You are not allowed to use copy method on singleton!")}
For the EventManager
class, I created the private
initializer so that no one could create a new copy of the object. I also added the sharedInstance
static variable and initialized it with the EventManager()
object. Then, to avoid copying the object, I rewrote the copy()
and mutableCopy()
methods. In earlier versions of Swift, you could use dispatch_once{}
, but in swift 3.0, this function is no longer available.
Factory Method (Creational category)
The factory method is used when it is necessary to make a choice between classes that implement a common protocol or share a common base class. This pattern contains a logic that decides which class to choose.
All logic, as the template name suggests, is in the method that encapsulates the solution.
We have two implementation options: to use the global method or base class.
Global method:
func createRentalCar(_ passengers:Int)-> RentalCar? { var carImp: RentalCar.Type? switch passengers {case0...3: carImp = Compact.self case4...8: carImp = SUV.self default: carImp =nil}return carImp?.createRentalCar(passengers)}
The base class use involves moving this logic to the base class.
Abstract Factory (Creational category)
The abstract factory pattern is very similar to the one described above, except that it’s used to create a group of objects. The pattern cannot forecast which implementation will be used, but it helps you select the appropriate specific object.
//heart of pattern final class func getFactory(car: Cars)-> CarFactory? { var factory: CarFactory? switch car {case .compact: factory = CompactCarFactory()case .sports: factory = SportsCarFactory()case .SUV: factory = SUVCarFactory()}return factory }
This method is situated in the base abstract class. We can use it in the structure of Car
:
struct Car { var carType: Cars var floor: FloorPlan var suspension: Suspension var drive: Drivetrain init(carType: Cars){ let concreteFactory = CarFactory.getFactory(car: carType) self.floor= concreteFactory!.createFloorplan() self.suspension = concreteFactory!.createSuspension() self.drive = concreteFactory!.createDrivetrain() self.carType = carType } func printDetails(){ print("Car type: \(carType.rawValue)") print("Seats: \(floor.seats)") print("Engine: \(floor.enginePosition.rawValue)") print("Suspension: \(suspension.suspensionType.rawValue)") print("Drive: \(drive.driveType.rawValue)")}}
Builder (Creational category)
Builder is an iOS design pattern used to separate the configuration from the object creation. The calling object contains configuration data and transfers it to the Builder object that is responsible for creating the object. Let’s imagine we have a restaurant and we need to create an application to order burgers:
let builder = BurgerBuilder() //1 stage let name ="Joe" //2 stage builder.setVeggie(choice:false) //3 stage builder.setMayo(choice:false) builder.setCooked(choice: .welldone) //4 stage builder.addPatty(choice:true) let order = builder.buildObject(name: name) order.printDescription()
In order to avoid polling visitors about every ingredient for their burgers, we will create default values in the Builder class.
… private var veggie =false private var pickles =false private var mayo =true private var ketchup =true private var lettuce =true private var cooked = Burger.Cooked.normal private var patties =2 private var bacon =true …
If it turns out that customers want to add mayonnaise, then we will change the default value to true
in the Builder-class. Furthermore, the customer polling time remains the same.
MVC (Structural category)
MVC is another iOS pattern. MVC stands for Model-View-Controller. The model works only with data (model). The view works with everything that implies drawing interface elements and animating different buttons. However, it’s better to use a separate class for the animation: you may want to change something later). At last, controller «collects» all this together.
Over time, controller will become more complex because of the logic related with the model. So you will have to use a more advanced pattern called model-view-viewModel (MVVM). Learn more about the MVVM pattern following the link.
Facade (Structural category)
Facade is another representative of iOS app design patterns. It offers one simplified interface for complex systems. Instead of showing numerous methods with different interfaces, we should create our own class while encapsulating other objects in it to provide a user with a more simplified interface.
This pattern is useful when you have to replace, for example, Alamofire
with NSURLSession
. You should make a change only in your Facade class without editing its interface.
The interface example with the SocketManager class:
final internal class SocketManager :NSObject{ static internal let sharedInstance: SocketManager.SocketManager override internal func copy()-> Any override internal func mutableCopy()-> Any internal var connectionWasOpened: Bool internal var lastUserId: String internal func openNewConnection(userId: String) internal func closeConnection() internal func logoutFromWebSocket() internal func reconnect()}
An end user may not know that we’re using the SocketRocket. Later, I can replace it with something else and I won’t have to make changes in all the places where it was used. It ill be enough to edit only one class.
Decorator (Structural category)
Among iOS app patterns, there also is the one called Decorator. It adds the necessary behavior and responsibilities to the object without modifying its code. It can be useful when, for example, we use third-party libraries and don’t have access to the source code.
ВIn Swift, there are two common implementations of this pattern: extensions и delegation.
Adapter (Structural category)
The adapter allows classes with incompatible interfaces to work together. Apple implements this template using protocols. The adapter is used when you need to integrate a component that has the code which cannot be modified. This pattern is useful when we deal with a legacy product. Since we don’t know how it works, we avoid modifying it.
Bridge (Structural category)
Bridge is another representative of iOS application design patterns. It’s very similar to the Adapter, but it has a few differences. In case with Bridge, we can modify the source code since we have access to it. The pattern separates the abstraction from the implementation so that they can be changed without corresponding changes in another class. For example:
protocol Switch { var appliance: Appliance {get set} func turnOn()} protocol Appliance { func run()} class RemoteControl: Switch { var appliance: Appliance func turnOn(){ self.appliance.run()} init(appliance: Appliance){ self.appliance = appliance }} class TV: Appliance { func run(){ print("tv turned on"); }} class VacuumCleaner: Appliance { func run(){ print("vacuum cleaner turned on")}} //===main.swift==== var vacCleaner = VacuumCleaner() var control = RemoteControl(appliance: vacCleaner) control.turnOn()
Now you can change the run()
methods without making corrections to the main file.
Observer (Behavioral category)
Observer implies objects notifying other ones about changing their state. One object usually «signs» for changes of the other one. Push notifications are a global example. A more local example is notifications and Key-Value Observing (KVO).
Momento (Behavioral category)
The Momento saves your objects, such as UserDefaults
, Archiving
and NSCoding protocol
, using the CoreData
.
Command (Behavioral category)
When we connect a method to an action touch for any interface (e.g. buttons), this is the Command pattern.
Final Thoughts
You can find more in the following articles:
https://www.raywenderlich.com/46988/ios-design-patterns
https://github.com/ochococo/Design-Patterns-In-Swift
And book:
Pro Design Patterns in Swift by Adam Freeman.