Skip to main content

23 questions tagged with "Design Patterns"

Design Patterns tag description

View All Tags

Explain the Flyweight Pattern in iOS

· 3 min read
Szymon Michalak
iOS Developer
Sources & Resources

Main Source: Ray Wenderlich - Design Patterns by Tutorials (2019)
Further Reading:

TL/DR

The Flyweight Pattern minimizes memory usage by sharing data between multiple objects. It is particularly useful when working with a large number of similar objects.

Concept Overview​

The Flyweight Pattern is a structural design pattern that helps reduce memory consumption by sharing as much data as possible between similar objects. It consists of:

  1. Flyweight: The shared object that contains the common data.
  2. Flyweight Factory: Manages flyweights and ensures that new flyweights are only created if they don't already exist.

Playground Example​

The following example demonstrates the Flyweight Pattern using UIKit classes such as UIColor:

import UIKit

// Example of flyweights in action
let red = UIColor.red
let red2 = UIColor.red
print(red === red2) // Output: true

// Custom colors are not flyweights
let color1 = UIColor(red: 1, green: 0, blue: 0, alpha: 1)
let color2 = UIColor(red: 1, green: 0, blue: 0, alpha: 1)
print(color1 === color2) // Output: false

In this example, UIColor.red returns the same instance because it uses the flyweight pattern to minimize memory usage. However, creating custom colors always results in different instances.

You can extend the UIColor class to implement a custom flyweight:

extension UIColor {
static var colorStore: [String: UIColor] = [:]

class func rgba(_ red: CGFloat, _ green: CGFloat, _ blue: CGFloat, _ alpha: CGFloat) -> UIColor {
let key = "\(red)-\(green)-\(blue)-\(alpha)"
if let color = colorStore[key] {
return color
}
let color = UIColor(red: red, green: green, blue: blue, alpha: alpha)
colorStore[key] = color
return color
}
}

// Usage
let flyColor1 = UIColor.rgba(1, 0, 0, 1)
let flyColor2 = UIColor.rgba(1, 0, 0, 1)
print(flyColor1 === flyColor2) // Output: true

How It Works:​

  • Flyweight: In this example, the UIColor class is extended to act as a flyweight. The colors are cached in colorStore, and when a request is made, the existing color is reused instead of creating a new instance.
  • Factory: The rgba method acts as a flyweight factory that ensures colors are only created once and reused.

When to Use​

  • Minimizing memory usage: When you have a large number of objects that share similar data and need to optimize memory consumption.

When to Be Careful​

  • Flyweight memory growth: If you store too many flyweights, the memory consumption could still become an issue. Implement strategies like memory warnings or cache limits to avoid excessive memory usage.

In Bullets
  • The Flyweight Pattern reduces memory usage by sharing objects.
  • It involves a flyweight and a factory for managing shared instances.
  • Particularly useful for caching, object pools, or any situation with repeated similar objects.

Explain the Iterator Pattern in iOS

· 3 min read
Szymon Michalak
iOS Developer
Sources & Resources

Main Source: Ray Wenderlich - Design Patterns by Tutorials (2019)
Further Reading:

TL/DR

The Iterator Pattern provides a standard way to traverse through a collection of objects without exposing the underlying representation. It helps in making custom objects iterable using Swift’s for-in syntax.

Concept Overview​

The Iterator Pattern involves the following key elements:

  1. Iterator: The object that provides the mechanism to traverse the collection.
  2. Iterable Collection: The object or data structure that holds multiple elements.
  3. Client: The code that uses the iterator to loop through the elements.

In Swift, it is common to conform to the Sequence protocol, which itself conforms to IteratorProtocol, and this enables the usage of high-order functions like map, filter, etc., "for free."

How Iterator Works:​

  1. Iterator: Provides a method to get the next element from a collection.
  2. Collection: Creates an iterator to traverse its elements.
  3. Client: Uses the iterator to access elements in sequence.

Key Benefits:​

  • Encapsulation: Hides the underlying structure of the collection from the client.
  • Standardization: Provides a common way to traverse different types of collections.

Playground Example​

Here is an example of using the Iterator Pattern to build a queue system:

import Foundation

// MARK: - Iterator Collection (Queue)
public struct Queue<T> {
private var array: [T?] = []
private var head = 0

public var isEmpty: Bool {
return count == 0
}

public var count: Int {
return array.count - head
}

public mutating func enqueue(_ element: T) {
array.append(element)
}

public mutating func dequeue() -> T? {
guard head < array.count, let element = array[head] else {
return nil
}
array[head] = nil
head += 1
return element
}
}

// MARK: - Conforming to Sequence Protocol
extension Queue: Sequence {
public func makeIterator() -> IndexingIterator<ArraySlice<T?>> {
let nonEmptyValues = array[head..<array.count]
return nonEmptyValues.makeIterator()
}
}

// Usage Example
var queue = Queue<String>()
queue.enqueue("Task 1")
queue.enqueue("Task 2")
queue.enqueue("Task 3")

for task in queue {
print(task ?? "No Task")
}

How It Works:​

  • Iterator: The iterator is created by conforming the Queue struct to the Sequence protocol.
  • Collection: The Queue struct holds the elements and provides the makeIterator() method.
  • Client: The for-in loop is used to iterate over the elements in the queue.

When to Use​

  • Custom Collections: When you need to make a custom collection of objects iterable using standard Swift syntax.
  • Abstraction: When you want to abstract the traversal logic of a complex object from the client.

When to Be Careful​

  • IteratorProtocol vs. Sequence: It’s better to conform to Sequence rather than implementing IteratorProtocol directly, as Sequence provides higher-order functions like map and filter.

In Bullets
  • The Iterator Pattern provides a standard way to iterate over a collection.
  • Commonly implemented by conforming to Swift's Sequence protocol.
  • Useful for traversing custom collections using the for-in syntax.

Explain the MVVM Pattern in iOS

· 4 min read
Szymon Michalak
iOS Developer
Sources & Resources

Main Source: Ray Wenderlich - Design Patterns by Tutorials (2019)
Further Reading:

TL/DR

The Model-View-ViewModel (MVVM) Pattern separates an app’s logic into three components: Model, View, and ViewModel. It helps transform models into values suitable for display in a view and is used to reduce the complexity of view controllers.

Concept Overview​

The MVVM Pattern consists of three main components:

  1. Model: Represents the data or the state of the app. It’s often implemented as simple structs or classes.
  2. View: Represents the UI elements responsible for displaying data to the user.
  3. ViewModel: Transforms the data in the model into information that the view can present.

This pattern helps avoid overloading view controllers, often called "Massive View Controllers" (MVC), by offloading the responsibility of transforming model data to the view model.

How MVVM Works:​

  1. Model: Contains the raw data and the logic to update it.
  2. ViewModel: Transforms raw model data into a format that is easier for the view to present, often handling computed properties like date formats.
  3. View: Displays the processed data from the view model.

Key Benefits:​

  • Simplified View Controllers: Offloads much of the business logic to the view model, reducing the complexity of view controllers.
  • Separation of Concerns: Maintains a clear division between the UI, data, and logic.

Playground Example​

Here’s an example of using the MVVM pattern to build a "Pet View" app for displaying pet details:

import UIKit

// MARK: - Model
public class Pet {
public let name: String
public let birthday: Date
public let rarity: Rarity
public let image: UIImage

public init(name: String, birthday: Date, rarity: Rarity, image: UIImage) {
self.name = name
self.birthday = birthday
self.rarity = rarity
self.image = image
}

public enum Rarity {
case common, uncommon, rare, veryRare
}
}

// MARK: - ViewModel
public class PetViewModel {
private let pet: Pet
private let calendar: Calendar

public init(pet: Pet) {
self.pet = pet
self.calendar = Calendar(identifier: .gregorian)
}

public var name: String {
return pet.name
}

public var ageText: String {
let components = calendar.dateComponents([.year], from: pet.birthday, to: Date())
let age = components.year ?? 0
return "\(age) years old"
}

public var adoptionFeeText: String {
switch pet.rarity {
case .common:
return "$50"
case .uncommon:
return "$75"
case .rare:
return "$150"
case .veryRare:
return "$500"
}
}

public var image: UIImage {
return pet.image
}
}

// Example Usage
let birthday = Calendar.current.date(byAdding: .year, value: -2, to: Date())!
let pet = Pet(name: "Whiskers", birthday: birthday, rarity: .veryRare, image: UIImage())
let viewModel = PetViewModel(pet: pet)

print(viewModel.name) // "Whiskers"
print(viewModel.ageText) // "2 years old"
print(viewModel.adoptionFeeText) // "$500"

How It Works:​

  • Model: The Pet class represents the raw data for the pet.
  • ViewModel: The PetViewModel class transforms data from the Pet model to provide formatted outputs for the view.
  • View: In a real app, the view would display the pet's name, age, and adoption fee.

When to Use​

  • Complex UI Logic: Use MVVM when the view requires complex transformations of model data, such as formatting dates, currencies, or other complex UI logic.
  • Reducing ViewController Complexity: When you find that your view controller is handling too much logic, MVVM can offload that responsibility.

When to Be Careful​

  • Overhead: While MVVM can simplify controllers, it may introduce more complexity if the view model isn’t carefully designed. Avoid unnecessary logic inside view models.

In Bullets
  • The MVVM Pattern separates concerns into Model, View, and ViewModel components.
  • It helps reduce view controller complexity by offloading business logic.
  • Useful for UI logic transformation, such as formatting data for display.

Explain the Mediator Pattern in iOS

· 3 min read
Szymon Michalak
iOS Developer
Sources & Resources

Main Source: Ray Wenderlich - Design Patterns by Tutorials (2019)
Further Reading:

TL/DR

The Mediator Pattern centralizes communication between objects (colleagues) by using a mediator that manages the interactions. This reduces coupling between objects and simplifies communication in complex systems.

Concept Overview​

The Mediator Pattern is a behavioral pattern that facilitates communication between multiple objects (colleagues) by delegating the interactions to a mediator. The key components of this pattern are:

  1. Mediator: Manages and coordinates communication between colleagues.
  2. Colleague: The objects that need to communicate, but interact only through the mediator.
  3. Mediator Protocol: Defines the interface for communication between the mediator and colleagues.

Playground Example​

The following example demonstrates the Mediator Pattern using a group of Musketeers who communicate via a mediator:

import Foundation

// MARK: - Colleague Protocol
public protocol Colleague: AnyObject {
func colleague(_ colleague: Colleague?, didSendMessage message: String)
}

// MARK: - Mediator Protocol
public protocol MediatorProtocol: AnyObject {
func addColleague(_ colleague: Colleague)
func sendMessage(_ message: String, by colleague: Colleague)
}

// MARK: - Colleague Class
public class Musketeer {
public var name: String
public weak var mediator: MediatorProtocol?

public init(mediator: MediatorProtocol, name: String) {
self.mediator = mediator
self.name = name
mediator.addColleague(self)
}

public func sendMessage(_ message: String) {
print("\(name) sent: \(message)")
mediator?.sendMessage(message, by: self)
}
}

// MARK: - Mediator Class
public class MusketeerMediator: MediatorProtocol {
private var colleagues: [Colleague] = []

public func addColleague(_ colleague: Colleague) {
colleagues.append(colleague)
}

public func sendMessage(_ message: String, by colleague: Colleague) {
colleagues.forEach { $0.colleague(colleague, didSendMessage: message) }
}
}

// Example usage:
let mediator = MusketeerMediator()
let athos = Musketeer(mediator: mediator, name: "Athos")
let porthos = Musketeer(mediator: mediator, name: "Porthos")
let aramis = Musketeer(mediator: mediator, name: "Aramis")

athos.sendMessage("One for all...!")

How It Works:​

  • Mediator: MusketeerMediator manages the communication between different Musketeer objects.
  • Colleague: Each Musketeer sends messages through the mediator, and the mediator distributes the messages to the other musketeers.

When to Use​

  • Simplifying communication: When multiple objects need to communicate, but you want to reduce their direct dependencies.
  • Decoupling: When you need to centralize control of interactions in complex systems.

When to Be Careful​

  • Mediator becoming complex: Be cautious about the mediator taking on too many responsibilities and becoming a "god object."

In Bullets
  • The Mediator Pattern centralizes communication between objects (colleagues).
  • It involves a mediator and colleagues who communicate only through the mediator.
  • Useful for decoupling interactions and managing communication in complex systems.

Explain the Memento Pattern in iOS

· 3 min read
Szymon Michalak
iOS Developer
Sources & Resources

Main Source: Ray Wenderlich - Design Patterns by Tutorials (2019)
Further Reading:

TL/DR

The Memento Pattern allows an object to save its state and restore it later. It is useful for undo features, game saves, or any application that requires state restoration.

Concept Overview​

The Memento Pattern consists of three primary components:

  1. Originator: The object whose state you want to save and restore.
  2. Memento: A representation of the stored state of the originator.
  3. Caretaker: Manages the memento's persistence and can restore the originator’s state when needed.

This pattern is useful in scenarios where an object needs to be saved and restored to a previous state, such as saving game progress, undo functionality, or state management across sessions.

How Memento Works:​

  1. Originator: Creates and restores its own state using the memento.
  2. Memento: Holds the state of the originator at a specific time.
  3. Caretaker: Requests the originator to save its state and provides the memento back when restoration is required.

Key Benefits:​

  • State Restoration: Allows saving and restoring an object’s state.
  • Encapsulation: Keeps the originator’s internal state hidden from external objects.

Playground Example​

Here is a simple example of using the Memento Pattern in a game:

import Foundation

// MARK: - Originator
public class Game: Codable {
public class State: Codable {
public var attemptsRemaining: Int = 3
public var level: Int = 1
public var score: Int = 0
}

public var state = State()

public func rackUpPoints() {
state.score += 1000
}

public func loseLife() {
state.attemptsRemaining -= 1
}
}

// MARK: - Caretaker
public class GameSystem {
private let encoder = JSONEncoder()
private let decoder = JSONDecoder()
private let userDefaults = UserDefaults.standard

public func save(_ game: Game, title: String) throws {
let data = try encoder.encode(game)
userDefaults.set(data, forKey: title)
}

public func load(title: String) throws -> Game {
guard let data = userDefaults.data(forKey: title),
let game = try? decoder.decode(Game.self, from: data) else {
throw Error.gameNotFound
}
return game
}

public enum Error: String, Swift.Error {
case gameNotFound
}
}

// Example usage
var game = Game()
game.loseLife()
game.rackUpPoints()

let gameSystem = GameSystem()
try? gameSystem.save(game, title: "Save1")

game = Game()
print("New game score: \(game.state.score)")

game = try! gameSystem.load(title: "Save1")
print("Loaded game score: \(game.state.score)")

How It Works:​

  • Originator: The Game class is the originator, managing the game’s state.
  • Memento: The encoded state of the game, stored by the GameSystem.
  • Caretaker: The GameSystem class stores and retrieves the game’s state.

When to Use​

  • Undo/Redo Functionality: When you need to revert an object to a previous state.
  • State Persistence: For saving and loading application states across sessions, such as game saves or document states.

When to Be Careful​

  • Overhead: Saving and restoring large or complex objects can have performance implications.
  • Versioning Issues: When the structure of the saved object changes, you may encounter difficulties restoring previous states unless properly managed.

In Bullets
  • The Memento Pattern allows saving and restoring an object’s state.
  • It is composed of an originator, memento, and caretaker.
  • Useful for undo/redo functionality, game saves, and state persistence.

Explain the Multicast Delegate Pattern in iOS

· 4 min read
Szymon Michalak
iOS Developer
Sources & Resources

Main Source: Ray Wenderlich - Design Patterns by Tutorials (2019)
Further Reading:

TL/DR

The Multicast Delegate Pattern allows one object to notify multiple delegates about an event, instead of having a one-to-one relationship like a traditional delegate pattern. This is useful when multiple objects need to respond to the same event.

Concept Overview​

The Multicast Delegate Pattern consists of four main components:

  1. Delegating Object: The object that has one or more delegates.
  2. Delegate Protocol: Defines the methods a delegate should implement.
  3. Delegates: The objects that implement the delegate protocol.
  4. Multicast Delegate: A helper class that manages multiple delegates and notifies them about events.

This pattern is useful for broadcasting updates to multiple objects, such as notifying several UI components or services about a change.

How Multicast Delegate Works:​

  1. Delegating Object: Holds a reference to the MulticastDelegate class and uses it to notify multiple delegates.
  2. Multicast Delegate: Manages an array of delegates and forwards calls to them.
  3. Delegate Protocol: All delegates conform to this protocol and implement its methods.

Key Benefits:​

  • One-to-Many Communication: A single event can trigger responses from multiple objects.
  • Decoupling: Keeps the delegating object decoupled from the logic of the delegates.

Playground Example​

Here’s an example of using the Multicast Delegate Pattern in an emergency response system:

import Foundation

// MARK: - Delegate Protocol
public protocol EmergencyResponding {
func notifyFire(at location: String)
func notifyCarCrash(at location: String)
}

// MARK: - Multicast Delegate
public class MulticastDelegate<ProtocolType> {
private class DelegateWrapper {
weak var delegate: AnyObject?

init(_ delegate: AnyObject) {
self.delegate = delegate
}
}

private var delegateWrappers: [DelegateWrapper] = []

public var delegates: [ProtocolType] {
return delegateWrappers.compactMap { $0.delegate } as! [ProtocolType]
}

public func addDelegate(_ delegate: ProtocolType) {
let wrapper = DelegateWrapper(delegate as AnyObject)
delegateWrappers.append(wrapper)
}

public func removeDelegate(_ delegate: ProtocolType) {
guard let index = delegateWrappers.firstIndex(where: { $0.delegate === (delegate as AnyObject) }) else { return }
delegateWrappers.remove(at: index)
}

public func invokeDelegates(_ closure: (ProtocolType) -> Void) {
for delegate in delegates {
closure(delegate)
}
}
}

// MARK: - Delegates
public class FireStation: EmergencyResponding {
public func notifyFire(at location: String) {
print("Firefighters were notified about a fire at \(location)")
}

public func notifyCarCrash(at location: String) {
print("Firefighters were notified about a car crash at \(location)")
}
}

public class PoliceStation: EmergencyResponding {
public func notifyFire(at location: String) {
print("Police were notified about a fire at \(location)")
}

public func notifyCarCrash(at location: String) {
print("Police were notified about a car crash at \(location)")
}
}

// MARK: - Delegating Object
public class DispatchSystem {
let multicastDelegate = MulticastDelegate<EmergencyResponding>()
}

// Example usage
let dispatch = DispatchSystem()
let policeStation = PoliceStation()
let fireStation = FireStation()

dispatch.multicastDelegate.addDelegate(policeStation)
dispatch.multicastDelegate.addDelegate(fireStation)

dispatch.multicastDelegate.invokeDelegates { $0.notifyFire(at: "Main Street") }

How It Works:​

  • Multicast Delegate: Manages a list of weakly referenced delegates and ensures that each delegate receives the notification.
  • Delegates: FireStation and PoliceStation implement the EmergencyResponding protocol to react to notifications.

When to Use​

  • One-to-Many Delegate Relationships: When you need to notify multiple objects about the same event.
  • Decoupling Logic: When you want to decouple the sender of an event from the receivers.

When to Be Careful​

  • Information Only: This pattern works best for notifications. It’s not suitable when you need data from delegates, as multiple responses can cause conflicts.

In Bullets
  • The Multicast Delegate Pattern enables one-to-many delegate relationships.
  • Involves a delegating object, delegate protocol, delegates, and a multicast delegate.
  • Useful for broadcasting events to multiple objects without creating tight dependencies.

Explain the Observer Pattern in iOS

· 3 min read
Szymon Michalak
iOS Developer
Sources & Resources

Main Source: Ray Wenderlich - Design Patterns by Tutorials (2019)
Further Reading:

TL/DR

The Observer Pattern allows one object to observe changes in another object. In Swift, this is often implemented using Combine with Publisher and Subscriber types.

Concept Overview​

The Observer Pattern is about one object observing changes in another object. It involves three main roles:

  • Subscriber: The "observer" object that receives updates.
  • Publisher: The "observable" object that sends updates.
  • Value: The underlying object whose changes are being observed.

The pattern is often used in conjunction with MVC, where the model (Publisher) communicates changes back to the view controller (Subscriber) without tight coupling between the two.

How the Observer Pattern Works:​

  1. Publisher: An object that owns a value and notifies subscribers of changes.
  2. Subscriber: An object that listens for updates to the value.
  3. Combine Framework: Swift’s framework for handling asynchronous events, used to implement the observer pattern with @Published and sink().

Key Benefits:​

  • Decoupling: Separates concerns by allowing the model and view controller to remain unaware of each other's specific types.
  • Reactivity: Automatically updates the UI in response to changes in the model, keeping everything synchronized.

Playground Example​

Here is an example using Swift’s Combine framework:

import Combine

// Publisher: User
public class User {
@Published var name: String

public init(name: String) {
self.name = name
}
}

// Example usage
let user = User(name: "Ray")
let publisher = user.$name

var subscriber: AnyCancellable? = publisher.sink { value in
print("User's name is \(value)")
}

user.name = "Vicki"
subscriber = nil
user.name = "Ray has left the building"

How It Works:​

  • Publisher: The User class publishes changes to its name property with the @Published annotation.
  • Subscriber: The sink() method listens for changes to user.name.
  • Reactivity: The subscriber receives the updated value and prints it to the console.

When to Use​

  • Changes in Model: Use the Observer Pattern whenever changes to a model need to be reflected elsewhere in the app, such as the view or controller.
  • Reactivity: When you want the UI to react to changes automatically without needing to manually update it.

When to Be Careful​

  • Overuse of @Published: Not every property needs to be observable. Be selective about which properties should be marked as @Published to avoid performance overhead and complexity.
  • Tight Coupling: Ensure that you're not introducing tight coupling between components, which defeats the purpose of the Observer Pattern.

In Bullets
  • Observer Pattern enables communication between objects by allowing one to observe changes in another.
  • Swift’s Combine framework provides Publisher and Subscriber for easy implementation.
  • Be mindful of overuse of @Published properties.

Explain the Prototype Pattern in iOS

· 3 min read
Szymon Michalak
iOS Developer
Sources & Resources

Main Source: Ray Wenderlich - Design Patterns by Tutorials (2019)
Further Reading:

TL/DR

The Prototype Pattern is a creational pattern that allows objects to clone themselves, providing a way to create new instances without knowing the exact type of the object beforehand. This pattern is useful when object creation is complex or resource-intensive.

Concept Overview​

The Prototype Pattern consists of two main components:

  1. Prototype: The object that provides the cloning functionality by implementing the copy() method.
  2. Client: The entity that uses the prototype to create new instances by copying the original object.

This pattern is often used in iOS development when dealing with objects that are expensive to create or when you need to generate copies of an object without depending on its exact type.

How Prototype Works:​

  1. Prototype: Declares the copy() method to create a clone of the object.
  2. Concrete Prototype: Implements the cloning method, ensuring that the correct type and attributes are copied.
  3. Client: Uses the prototype to clone the object without knowing the exact type of the object.

Key Benefits:​

  • Efficient Object Creation: Allows the creation of new objects by copying existing ones, avoiding the need for expensive object initialization.
  • Encapsulation: Hides the details of object creation from the client.

Playground Example​

Here’s an example of using the Prototype Pattern to clone game characters:

import Foundation

// MARK: - Prototype Protocol
public protocol Copying {
init(_ prototype: Self)
}

extension Copying {
public func copy() -> Self {
return type(of: self).init(self)
}
}

// MARK: - Concrete Prototype
public class Character: Copying {
public var name: String
public var health: Int
public var level: Int

public init(name: String, health: Int, level: Int) {
self.name = name
self.health = health
self.level = level
}

public required convenience init(_ prototype: Character) {
self.init(name: prototype.name, health: prototype.health, level: prototype.level)
}
}

// Usage Example
let originalCharacter = Character(name: "Knight", health: 100, level: 5)
let clonedCharacter = originalCharacter.copy()

print("Original Character: \(originalCharacter.name), Level: \(originalCharacter.level)")
print("Cloned Character: \(clonedCharacter.name), Level: \(clonedCharacter.level)")

How It Works:​

  • Prototype: The Character class implements the Copying protocol, providing a way to clone itself.
  • Concrete Prototype: The cloned Character object is identical to the original, with all its attributes copied.
  • Client: The client code calls copy() to clone the character.

When to Use​

  • Complex Object Creation: When the process of creating an object is resource-intensive or involves complex setup.
  • Object Duplication: When you need to create multiple copies of an object, such as game characters or data models.

When to Be Careful​

  • Shallow vs. Deep Copy: Be mindful of whether you're performing a shallow copy (copying references) or a deep copy (duplicating the entire object).

In Bullets
  • The Prototype Pattern allows objects to clone themselves to create new instances.
  • It involves a prototype and a client that requests a copy.
  • Useful for efficient object duplication and reducing object creation costs.

Explain Singleton Pattern in iOS

· 3 min read
Szymon Michalak
iOS Developer
Sources & Resources

Main Source: Ray Wenderlich - Design Patterns by Tutorials (2019)

Further Reading:

TL/DR

The Singleton Pattern restricts a class to a single instance and provides a global point of access to that instance. It is often used in iOS for managing shared resources or ensuring consistency throughout the app.

Concept Overview​

The Singleton Pattern ensures that a class has only one instance, providing a global access point to that instance. It’s used frequently in iOS for things like app settings, managing global states, and coordinating shared resources. This pattern comes in two forms:

  • Singleton: Only a single instance of the class exists.
  • Singleton Plus: There’s a shared instance, but additional instances can be created if needed.

How Singleton Works:​

  1. Private Constructor: The constructor is private to prevent direct instantiation.
  2. Shared Instance: A static property provides access to the single instance.
  3. Lazy Instantiation: The instance is only created when it is first accessed.

Key Benefits:​

  • Global Access: Provides a shared instance that can be accessed from anywhere in the app.
  • Consistency: Ensures that only one instance of a class exists, which is important for managing shared resources or states.

Playground Example​

Here’s an example of using the Singleton Pattern in an iOS app:

import Foundation

// MARK: - Singleton
public class AppSettings {
// Static shared instance
public static let shared = AppSettings()

// Private initializer to prevent additional instances
private init() { }

// Example properties
public var theme: String = "Light"
public var language: String = "English"
}

// Usage
let settings = AppSettings.shared
settings.theme = "Dark"
print("App theme is: \(settings.theme)")

How It Works:​

  • Private Constructor: AppSettings uses a private initializer to prevent creating multiple instances.
  • Shared Access: The static shared instance allows global access to the settings.

When to Use​

  • Shared Resources: Use the Singleton Pattern when you need to manage shared resources (e.g., settings, cache) across the entire app.
  • Global State: When you need to enforce consistency, ensuring only one instance of the class is active.

When to Be Careful​

  • Overuse: Singletons can introduce hidden dependencies and make unit testing difficult due to the global state they create.
  • Testing Challenges: Mocking or replacing singletons in tests can be tricky, as they often introduce tightly coupled dependencies.

In Bullets
  • Singleton Pattern restricts a class to one instance and provides a global access point.
  • It is used for shared resources and global states in iOS apps.
  • Be cautious of overuse and the potential impact on unit testing.

Explain the State Pattern in iOS

· 3 min read
Szymon Michalak
iOS Developer
Sources & Resources

Main Source: Ray Wenderlich - Design Patterns by Tutorials (2019)
Further Reading:

TL/DR

The State Pattern allows an object to change its behavior based on its current state. It helps to avoid large switch or if-else statements and promotes better organization of state-based logic. This pattern is useful in systems that have multiple states, such as animations, game mechanics, or UI changes.

Concept Overview​

The State Pattern consists of three primary components:

  1. Context: The object that holds the current state and delegates behavior to the current state.
  2. State Protocol: Defines the required methods and properties for states.
  3. Concrete States: Implement the behavior associated with a particular state.

In iOS, the State Pattern can be used to handle various states of an object, such as a traffic light system or the different stages of an animation.

How State Works:​

  1. Context: Maintains a reference to the current state.
  2. Concrete States: Each state implements different behavior that the context delegates to.
  3. State Protocol: Ensures all states have the required methods.

Key Benefits:​

  • Encapsulation: The logic for each state is separated into different classes, promoting better organization.
  • Flexibility: Adding new states or modifying behavior becomes easier without changing the context class itself.

Playground Example​

Here is an example of using the State Pattern to implement a traffic light system:

import Foundation

// MARK: - State Protocol
public protocol TrafficLightState {
func handle(context: TrafficLight)
}

// MARK: - Concrete States
public class GreenState: TrafficLightState {
public func handle(context: TrafficLight) {
print("Green Light - Cars can go.")
context.transition(to: YellowState())
}
}

public class YellowState: TrafficLightState {
public func handle(context: TrafficLight) {
print("Yellow Light - Cars should prepare to stop.")
context.transition(to: RedState())
}
}

public class RedState: TrafficLightState {
public func handle(context: TrafficLight) {
print("Red Light - Cars must stop.")
context.transition(to: GreenState())
}
}

// MARK: - Context
public class TrafficLight {
private var state: TrafficLightState

public init(initialState: TrafficLightState) {
self.state = initialState
}

public func request() {
state.handle(context: self)
}

public func transition(to newState: TrafficLightState) {
self.state = newState
}
}

// Example usage
let trafficLight = TrafficLight(initialState: GreenState())
trafficLight.request() // Green -> Yellow
trafficLight.request() // Yellow -> Red
trafficLight.request() // Red -> Green

How It Works:​

  • Context: The TrafficLight class maintains a reference to the current state and delegates behavior to it.
  • State Transition: Each concrete state (such as GreenState) defines what the next state should be and transitions the context to it.
  • State Protocol: Ensures all states implement the handle() method.

When to Use​

  • State-Based Behavior: When an object’s behavior depends on its current state and needs to change dynamically.
  • Avoiding Large Conditionals: If your code relies on many switch or if-else statements based on state, the State Pattern can provide a cleaner solution.

When to Be Careful​

  • Overhead: Be cautious if the number of states is small and the logic is simple, as introducing the State Pattern may introduce unnecessary complexity.

In Bullets
  • The State Pattern allows objects to change behavior based on their state.
  • Components include context, state protocol, and concrete states.
  • Useful for systems with state-based logic such as traffic lights, animations, or game mechanics.