SOLID Principles in Swift: Theory and Practice for Advanced iOS Developers

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: Why SOLID Matters for a Swift Developer

In the world of iOS development, creating maintainable and scalable code is a key task. The SOLID principles, formulated by Robert Martin, remain the gold standard of software architecture. At an advanced level, mastering these principles in Swift allows you not only to write clean code but also to build flexible systems that are resilient to changing requirements. In this article, we will break down each principle with examples in Swift, focusing on real-world tasks of an iOS/macOS developer.



1. Single Responsibility Principle (SRP) in Swift

Theory: Each class or structure should have only one reason to change. In practice, this means that a module is responsible for one clearly defined task.



Problem: Overloaded ViewController

In UIKit, you often encounter controllers that simultaneously manage the UI, handle network requests, and save data. This violates SRP.

// ❌ SRP Violationclass UserViewController: UIViewController {    @IBOutlet weak var tableView: UITableView!    private var users: [User] = []

override func viewDidLoad() { super.viewDidLoad() fetchUsers() }

private func fetchUsers() { // Network request URLSession.shared.dataTask(with: URL(string: "https://api.example.com/users")!) { data, _, _ in // JSON parsing // Saving to Core Data DispatchQueue.main.async { self.tableView.reloadData() } }.resume() }}


Solution: Separation of Responsibilities

Move data and network work into separate services.

// ✅ SRP: separate service for working with usersprotocol UserServiceProtocol {    func fetchUsers(completion: @escaping (Result<[User], Error>) -> Void)}

class UserService: UserServiceProtocol { func fetchUsers(completion: @escaping (Result<[User], Error>) -> Void) { // Network request logic }}

// ✅ SRP: separate manager for Core Dataclass CoreDataManager { func saveUsers(_ users: [User]) { // Saving to Core Data }}

// ✅ SRP: ViewController only manages the UIclass UserViewController: UIViewController { private let userService: UserServiceProtocol private let coreDataManager: CoreDataManager

init(userService: UserServiceProtocol, coreDataManager: CoreDataManager) { self.userService = userService self.coreDataManager = coreDataManager super.init(nibName: nil, bundle: nil) }

override func viewDidLoad() { super.viewDidLoad() loadUsers() }

private func loadUsers() { userService.fetchUsers { [weak self] result in switch result { case .success(let users): self?.coreDataManager.saveUsers(users) self?.updateUI(with: users) case .failure(let error): self?.showError(error) } } }}


2. Open/Closed Principle (OCP) and Swift Protocols

Theory: Software entities (classes, modules) should be open for extension but closed for modification. In Swift, this is achieved through protocols and inheritance.



Example: Notification System

// ❌ OCP Violation: adding a new notification type requires changing the switchclass NotificationSender {    func send(_ notification: Notification, type: String) {        switch type {        case "email":            // Sending email        case "push":            // Sending push        default:            break        }    }}

// ✅ OCP: using a protocolprotocol NotificationChannel { func send(_ notification: Notification)}

class EmailChannel: NotificationChannel { func send(_ notification: Notification)

Blogs

Book Recommendations