Skip to main content

23 questions tagged with "Design Patterns"

Design Patterns tag description

View All Tags

Explain Strategy 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 Strategy Pattern defines a family of interchangeable algorithms that can be selected at runtime. The pattern allows for different behaviors (strategies) to be chosen and swapped without changing the overall structure of the object using the strategy.

Concept Overview​

The Strategy Pattern consists of three key parts:

  • Context: The object that utilizes a strategy to perform a task.
  • Strategy Protocol: Defines a set of methods that any strategy must implement.
  • Concrete Strategies: Specific implementations of the strategy protocol that contain the actual algorithm or behavior.

The Strategy Pattern is commonly used when you need interchangeable behavior in your application without hard-coding decision logic into the context object. It allows you to select and switch between different strategies dynamically at runtime.

How Strategy Works:​

  1. Context Object: The context holds a reference to a strategy object and delegates behavior to it.
  2. Concrete Strategies: These classes implement the strategy protocol with specific behaviors.
  3. Interchangeability: Strategies can be swapped at runtime, making the behavior of the context flexible.

Key Benefits:​

  • Open/Closed Principle: New strategies can be added without modifying the existing codebase.
  • Separation of Concerns: Each strategy focuses on a specific task, leading to cleaner code.
  • Reusability: Strategies can be reused in different contexts.

Playground Example​

Here’s an example demonstrating the Strategy Pattern in an app that uses various movie rating services:

import UIKit

// Strategy Protocol
public protocol MovieRatingStrategy {
var ratingServiceName: String { get }
func fetchRating(for movieTitle: String, success: (_ rating: String, _ review: String) -> ())
}

// Concrete Strategy 1: Rotten Tomatoes
public class RottenTomatoesClient: MovieRatingStrategy {
public let ratingServiceName = "Rotten Tomatoes"

public func fetchRating(for movieTitle: String, success: (_ rating: String, _ review: String) -> ()) {
// Dummy response for demonstration purposes
let rating = "95%"
let review = "It rocked!"
success(rating, review)
}
}

// Concrete Strategy 2: IMDb
public class IMDbClient: MovieRatingStrategy {
public let ratingServiceName = "IMDb"

public func fetchRating(for movieTitle: String, success: (_ rating: String, _ review: String) -> ()) {
// Dummy response for demonstration purposes
let rating = "8.7"
let review = "A masterpiece!"
success(rating, review)
}
}

// Context
public class MovieRatingService {
private var strategy: MovieRatingStrategy

init(strategy: MovieRatingStrategy) {
self.strategy = strategy
}

func changeStrategy(_ strategy: MovieRatingStrategy) {
self.strategy = strategy
}

func fetchRating(for movieTitle: String) {
print("Fetching rating from \(strategy.ratingServiceName)")
strategy.fetchRating(for: movieTitle) { (rating, review) in
print("Rating: \(rating), Review: \(review)")
}
}
}

// Usage
let movieService = MovieRatingService(strategy: RottenTomatoesClient())
movieService.fetchRating(for: "Inception")

movieService.changeStrategy(IMDbClient())
movieService.fetchRating(for: "Inception")

How It Works:​

  • Strategy Protocol: MovieRatingStrategy defines the structure all strategies must follow.
  • Concrete Strategies: RottenTomatoesClient and IMDbClient implement the protocol with their respective algorithms.
  • Context: The MovieRatingService uses the strategy pattern to fetch movie ratings. The strategies can be swapped dynamically.

When to Use​

  • Multiple Behaviors: When an object needs to exhibit multiple behaviors or use different algorithms, and these should be interchangeable.
  • Avoiding Conditional Logic: Instead of hard-coding different behaviors using complex conditionals, the strategy pattern allows for cleaner and more maintainable code by delegating the behavior to separate classes.

When to Be Careful​

  • Overhead: If overused, the strategy pattern can introduce overhead with too many classes.
  • Complex Strategy Selection: You still need a mechanism to decide which strategy to use, which can sometimes be complex.

In Bullets
  • Strategy Pattern allows interchangeable behaviors (strategies) at runtime.
  • Context holds a reference to a strategy and delegates behavior to it.
  • The pattern promotes the Open/Closed Principle by allowing new strategies without modifying existing code.

Explain the Coordinator Pattern and Its Benefits

· 2 min read
Szymon Michalak
iOS Developer
Sources & Resources

Main Source: 🔗 Coordinator Pattern in iOS

Additional Sources:

Further Reading:

TL/DR

The Coordinator pattern is a design pattern in iOS development that helps manage navigation flow and dependency between view controllers, leading to better-organized, more modular, and testable code.

The Coordinator pattern is an architectural pattern used in iOS development to manage the flow of navigation within an application. Instead of letting view controllers handle the navigation, a separate coordinator object takes over this responsibility. This leads to a more organized structure where view controllers are only responsible for displaying content, and the coordinator handles the logic of navigating between screens.

Code Examples​

Here's a basic example of implementing the Coordinator pattern in Swift:

protocol Coordinator {
var childCoordinators: [Coordinator] { get set }
func start()
}

class MainCoordinator: Coordinator {
var childCoordinators = [Coordinator]()
var navigationController: UINavigationController

init(navigationController: UINavigationController) {
self.navigationController = navigationController
}

func start() {
let vc = ViewController()
vc.coordinator = self
navigationController.pushViewController(vc, animated: true)
}

func showDetail() {
let detailVC = DetailViewController()
detailVC.coordinator = self
navigationController.pushViewController(detailVC, animated: true)
}
}

In this example, MainCoordinator is responsible for initializing the first view controller and handling the navigation logic for showing a detail view controller.

Real-World Applications​

The Coordinator pattern is particularly beneficial in large-scale applications where complex navigation flows exist. By separating the navigation logic from view controllers, the code becomes easier to manage, test, and maintain. It also promotes reusability, as the same coordinator can handle similar navigation flows across different parts of the app.

Common Mistakes​

  • Tight Coupling: One common mistake when implementing the Coordinator pattern is allowing coordinators to become tightly coupled with specific view controllers, reducing flexibility.
  • Overcomplication: Sometimes, developers overcomplicate the Coordinator pattern by creating too many coordinators for simple flows, leading to unnecessary complexity.
In Bullets
  • Decouples Navigation from View Controllers: By moving navigation logic out of view controllers, the Coordinator pattern promotes cleaner and more modular code.
  • Improves Testability: Coordinators can be easily tested in isolation, leading to better test coverage for navigation logic.
  • Enhances Reusability: Coordinators can be reused across different parts of an application, especially in complex flows.

Explain Model-View-Controller (MVC) Pattern in iOS

· 5 min read
Szymon Michalak
iOS Developer
Sources & Resources

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

Further Reading:

TL/DR

The Model-View-Controller (MVC) pattern is a fundamental design pattern in iOS development that separates an app into three components:

  • Model: Handles the data and business logic.
  • View: Manages the UI elements.
  • Controller: Acts as the mediator between the Model and View, updating both as needed.

The Model-View-Controller (MVC) design pattern is a widely used architectural pattern in software development, particularly in iOS. It divides an application into three main components:

  • Model: Represents the data and the business logic of the application. The model is responsible for managing the data, whether it's from a database, API, or other sources. It is independent of the user interface.

  • View: The view is responsible for displaying the user interface and presenting data to the user. It observes the model for changes and updates the UI accordingly. In iOS, views are typically represented by UIView or UIViewController objects.

  • Controller: The controller acts as an intermediary between the model and the view. It handles user input, updates the model, and refreshes the view. Controllers in iOS are commonly represented by UIViewController classes.

How MVC Works:​

  1. User Interaction: The user interacts with the app through the view (e.g., tapping a button).
  2. Controller Handling: The controller captures this interaction and processes the input.
  3. Model Update: The controller updates the model based on the interaction.
  4. View Refresh: The view observes changes in the model and updates the UI accordingly.

Key Benefits:​

  • Separation of Concerns: Each component has a distinct responsibility, making the code easier to maintain and extend.
  • Reusability: Components can be reused across different parts of the application or in other projects.
  • Testability: With the logic separated, individual components can be tested independently.

Playground Example​

Here is a fully functional playground example demonstrating the MVC pattern in an address form application.

import UIKit

// MARK: - Address Model
public struct Address {
public var street: String
public var city: String
public var state: String
public var zipCode: String
}

// MARK: - Address View
public final class AddressView: UIView {
@IBOutlet public var streetTextField: UITextField!
@IBOutlet public var cityTextField: UITextField!
@IBOutlet public var stateTextField: UITextField!
@IBOutlet public var zipCodeTextField: UITextField!
}

// MARK: - Address View Controller
public final class AddressViewController: UIViewController {

// MARK: - Properties
public var address: Address?
public var addressView: AddressView! {
guard isViewLoaded else { return nil }
return (view as! AddressView)
}

// MARK: - View Lifecycle
public override func viewDidLoad() {
super.viewDidLoad()
updateViewFromAddress()
}

// MARK: - Helpers
private func updateViewFromAddress() {
guard let address = address else { return }
addressView.streetTextField.text = address.street
addressView.cityTextField.text = address.city
addressView.stateTextField.text = address.state
addressView.zipCodeTextField.text = address.zipCode
}

@IBAction public func updateAddressFromView(_ sender: AnyObject) {
guard
let street = addressView.streetTextField.text, street.count > 0,
let city = addressView.cityTextField.text, city.count > 0,
let state = addressView.stateTextField.text, state.count > 0,
let zipCode = addressView.zipCodeTextField.text, zipCode.count > 0
else {
// TODO: show an error message, handle the error, etc.
return
}

address = Address(street: street, city: city, state: state, zipCode: zipCode)
}
}

How It Works​

  • Model: The Address struct stores address information like street, city, state, and zip code.
  • View: The AddressView contains UITextField outlets that correspond to the address fields.
  • Controller: The AddressViewController manages the interaction between the model and the view. It updates the view when the model changes, and when the user inputs data, it updates the model accordingly.

When to Use​

  • Starting Point: MVC is an excellent starting pattern for structuring iOS applications. It provides a clear division of responsibilities that make apps more maintainable.
  • Simple Applications: This pattern works well for smaller projects or apps with simple user interfaces.
  • Reusability of Models and Views: If you need to reuse certain models and views across multiple parts of your app, MVC allows for that by decoupling the business logic from the UI.

When to Be Careful​

  • Massive View Controller (MVC): In larger applications, it's common for view controllers to become bloated with too much logic, which can make the app harder to maintain. This is often referred to as "Massive View Controller".
  • Not Always Flexible: MVC works well for simpler applications, but when apps grow in complexity, other patterns like MVVM (Model-View-ViewModel) or Coordinator can be added to keep logic from overloading the controllers.
In Bullets
  • MVC separates an application into Model, View, and Controller components.
  • The Model holds the data, the View manages the UI, and the Controller acts as the intermediary between them.
  • Overloading the Controller can lead to "Massive View Controller" syndrome, where the controller becomes difficult to manage.