Skip to main content

Explain the Facade 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 Facade Pattern provides a simplified interface to a complex system by hiding the complexity of subsystems, allowing consumers to interact with the system through a unified interface.

Concept Overview​

The Facade Pattern is a structural design pattern that simplifies interaction with complex systems by providing a single interface. It consists of:

  1. Facade: A simplified interface that interacts with multiple subsystem classes.
  2. Subsystem Classes: The actual implementation classes that perform specific tasks.

This pattern is especially useful when a system contains several interconnected components, and you want to expose a single entry point for users to perform tasks.

Playground Example​

The following example shows how the Facade Pattern works in an ordering system:

import Foundation

// MARK: - Dependencies
public struct Customer {
public let identifier: String
public var address: String
public var name: String
}

public struct Product {
public let identifier: String
public var name: String
public var cost: Double
}

// Inventory Database
public class InventoryDatabase {
public var inventory: [Product: Int] = [:]

public init(inventory: [Product: Int]) {
self.inventory = inventory
}
}

// Shipping Database
public class ShippingDatabase {
public var pendingShipments: [Customer: [Product]] = [:]
}

// MARK: - Facade
public class OrderFacade {
public let inventoryDatabase: InventoryDatabase
public let shippingDatabase: ShippingDatabase

public init(inventoryDatabase: InventoryDatabase, shippingDatabase: ShippingDatabase) {
self.inventoryDatabase = inventoryDatabase
self.shippingDatabase = shippingDatabase
}

public func placeOrder(for product: Product, by customer: Customer) {
print("Place order for '\(product.name)' by '\(customer.name)'")

let count = inventoryDatabase.inventory[product, default: 0]
guard count > 0 else {
print("'\(product.name)' is out of stock!")
return
}

inventoryDatabase.inventory[product] = count - 1

var shipments = shippingDatabase.pendingShipments[customer, default: []]
shipments.append(product)
shippingDatabase.pendingShipments[customer] = shipments

print("Order placed for '\(product.name)' by '\(customer.name)'")
}
}

// Example usage
let product = Product(identifier: "product-001", name: "Ray's Doodle", cost: 19.99)
let customer = Customer(identifier: "customer-001", address: "123 Apple St.", name: "Johnny Appleseed")
let inventoryDatabase = InventoryDatabase(inventory: [product: 50])
let shippingDatabase = ShippingDatabase()

let orderFacade = OrderFacade(inventoryDatabase: inventoryDatabase, shippingDatabase: shippingDatabase)
orderFacade.placeOrder(for: product, by: customer)

How It Works:​

  • Facade: OrderFacade simplifies the interaction by exposing a method placeOrder, which internally interacts with InventoryDatabase and ShippingDatabase.
  • Subsystem Classes: These handle specific responsibilities such as managing inventory and handling pending shipments.

When to Use​

  • Simplify Complex Systems: When you want to make complex systems easier to use.
  • Decouple Subsystems: When you need to hide the complexities of the subsystems from the client.

When to Be Careful​

  • God Facade: Avoid making the facade too complex. It should simplify the system without adding additional complexity or responsibility.

In Bullets
  • The Facade Pattern provides a simplified interface to a complex system.
  • It consists of a facade and subsystem classes.
  • Useful for simplifying interactions with complex components.

Explain the Factory 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 Factory Pattern is a creational pattern that provides a way to create objects without exposing the instantiation logic to the client. It delegates the responsibility of creating instances to a separate factory class.

Concept Overview​

The Factory Pattern consists of two main components:

  1. Factory: Handles the logic of object creation.
  2. Product: The object that is created by the factory.

This pattern is particularly useful in iOS for scenarios where you need to create objects with multiple configurations or polymorphic objects that implement a common interface or inherit from a base class.

How Factory Works:​

  1. Factory: The factory class contains the method to instantiate various types of products.
  2. Product: The objects returned by the factory, which could be any subtype of a common interface or superclass.

Key Benefits:​

  • Decouples Object Creation: Keeps object creation logic separate from the client.
  • Encapsulation: Simplifies client code by hiding instantiation details.

Playground Example​

Here is an example of using the Factory Pattern to create email responses for job applicants:

import Foundation

// MARK: - Models
public struct JobApplicant {
public let name: String
public let email: String
public var status: Status

public enum Status {
case new, interview, hired, rejected
}
}

public struct Email {
public let subject: String
public let messageBody: String
public let recipientEmail: String
public let senderEmail: String
}

// MARK: - Factory
public struct EmailFactory {
public let senderEmail: String

public func createEmail(to recipient: JobApplicant) -> Email {
let subject: String
let messageBody: String

switch recipient.status {
case .new:
subject = "We Received Your Application"
messageBody = "Thanks for applying, \(recipient.name)!"
case .interview:
subject = "Interview Invitation"
messageBody = "Dear \(recipient.name), please come for an interview."
case .hired:
subject = "Congratulations!"
messageBody = "Welcome to the team, \(recipient.name)!"
case .rejected:
subject = "Thank You for Applying"
messageBody = "Dear \(recipient.name), we regret to inform you that we have moved forward with other candidates."
}

return Email(subject: subject, messageBody: messageBody, recipientEmail: recipient.email, senderEmail: senderEmail)
}
}

// Usage Example
let applicant = JobApplicant(name: "John Doe", email: "john@example.com", status: .interview)
let emailFactory = EmailFactory(senderEmail: "hr@example.com")
let email = emailFactory.createEmail(to: applicant)

print(email.subject) // Interview Invitation

How It Works:​

  • Factory: The EmailFactory class is responsible for creating different types of emails based on the applicant’s status.
  • Product: The Email object is the product created by the factory.

When to Use​

  • Polymorphic Object Creation: When you have a group of related objects and need to manage their creation dynamically.
  • Simplifying Code: To reduce duplication by centralizing object creation in a single factory.

When to Be Careful​

  • Overuse: Simple object creation doesn’t always require a factory, so only use this pattern when needed.

In Bullets
  • The Factory Pattern simplifies object creation by delegating it to a factory.
  • It involves a factory and products that share a common interface or superclass.
  • Useful for managing complex creation logic, especially for polymorphic objects.

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.