Какой из принципов SOLID сложнее всего соблюдать на практике в iOS-разработке?

«Какой из принципов SOLID сложнее всего соблюдать на практике в iOS-разработке?» — вопрос из категории Софт-скиллы, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый пример ответа, который можно адаптировать под свой опыт.

Ответ

Наиболее сложным для последовательного соблюдения является Принцип инверсии зависимостей (Dependency Inversion Principle - DIP).

Формулировка: Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракций. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

Почему это сложно?

  1. Привычка к прямым зависимостям: В iOS SDK многие компоненты (URLSession, UserDefaults.standard, FileManager.default) представлены как синглтоны или конкретные классы, что провоцирует прямое использование.
  2. Дополнительная абстракция: Требует создания протоколов для каждого сервиса, что кажется избыточным в небольших задачах.
  3. Сложность внедрения: Реализация Dependency Injection (через инициализатор или property) увеличивает объем boilerplate-кода.
  4. Давление сроков: Под дедлайнами проще и быстрее нарушить принцип, чем правильно абстрагировать зависимости.

Пример нарушения DIP:

class DataManager {
    func fetchData() {
        // Прямая, жесткая зависимость от конкретной реализации
        URLSession.shared.dataTask(with: url) { ... }
    }
}

Пример соблюдения DIP:

// 1. Абстракция (протокол)
protocol NetworkService {
    func fetchData(from url: URL, completion: @escaping (Result<Data, Error>) -> Void)
}

// 2. Деталь (конкретная реализация)
class DefaultNetworkService: NetworkService {
    func fetchData(from url: URL, completion: @escaping (Result<Data, Error>) -> Void) {
        URLSession.shared.dataTask(with: url) { data, _, error in
            // ... обработка ответа
        }.resume()
    }
}

// 3. Модуль верхнего уровня зависит от абстракции
class DataManager {
    private let networkService: NetworkService

    // Внедрение зависимости через инициализатор
    init(networkService: NetworkService) {
        self.networkService = networkService
    }

    func loadData() {
        networkService.fetchData(from: someURL) { result in
            // ...
        }
    }
}
// Это делает код тестируемым (можно подменить NetworkService моком).