Introduction: Why SOLID is the Foundation of Quality Swift Code
Every iOS/macOS developer sooner or later faces a situation where the code becomes "spaghetti": changes in one place break the entire application, and testing turns into a nightmare. This is where SOLID principles come to the rescue — five fundamental rules of object-oriented design formulated by Robert Martin.
In this article, we will break down each SOLID principle using Swift examples, show how violating them leads to problems, and provide ready-made solutions. You will learn to write code that is easy to maintain, extend, and test.
1. Single Responsibility Principle (SRP)
Theory
SRP states: a class should have only one reason to change. In other words, each class is responsible for exactly one functionality.
SRP Violation (Bad Code)
class UserManager { func createUser(name: String, email: String) { // Create user saveToDatabase(user: User(name: name, email: email)) sendWelcomeEmail(to: email) logEvent("User created: \\(name)") } private func saveToDatabase(user: User) { /* ... */ } private func sendWelcomeEmail(to email: String) { /* ... */ } private func logEvent(_ message: String) { /* ... */ }}Here, UserManager is responsible for creation, saving, sending emails, and logging — four reasons to change.
Fix (Good Code)
class UserService { private let repository: UserRepository private let emailService: EmailService private let logger: Logger init(repository: UserRepository, emailService: EmailService, logger: Logger) { self.repository = repository self.emailService = emailService self.logger = logger } func createUser(name: String, email: String) { let user = User(name: name, email: email) repository.save(user) emailService.sendWelcome(to: email) logger.log("User created: \\(name)") }}
class UserRepository { func save(_ user: User) { /* saving only */ }}class EmailService { func sendWelcome(to email: String) { /* sending only */ }}class Logger { func log(_ message: String) { /* logging only */ }}Now each class has a single responsibility, making testing and maintenance easier.
2. Open/Closed Principle (OCP)
Theory
Classes should be open for extension but closed for modification. New functionality is added through inheritance or protocols, without touching existing code.
OCP Violation
class PaymentProcessor { func process(payment: Any, type: String) { if type == "card" { // Process card } else if type == "paypal" { // Process PayPal } // When adding a new type — modify this class }}Fix Using Protocols
protocol PaymentMethod { func process(amount: Double)}
class CardPayment: PaymentMethod { func process(amount: Double) { /* process card */ }}
class PayPalPayment: PaymentMethod { func process(amount: Double) { /* process PayPal */ }}
class PaymentProcessor { func process(payment: PaymentMethod, amount: Double) { payment.process(amount: amount) }}
// Add a new type without modifying existing codeclass ApplePayPayment: PaymentMethod { func process(amount: Double) { /* process Apple Pay */ }}Now, to add a new payment method, simply create a new class that implements the PaymentMethod protocol.