← Назад к вопросам

Приведи примеры UIKit классов которые противоречат SOLID

1.0 Junior🔥 161 комментариев
#UIKit и верстка

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

SOLID принципы и UIKit: анализ нарушений в исторических API

SOLID — это набор принципов объектно-ориентированного дизайна, направленных на создание устойчивого, масштабируемого и удобного в поддержке кода. UIKit, как фреймворк с долгой историей, содержит ряд классов и API, которые в разной степени противоречат этим принципам. Стоит отметить, что многие из этих нарушений связаны с эволюцией фреймворка и компромиссами между удобством, производительностью и обратной совместимостью.

Примеры нарушений в UIKit

1. UIViewController — нарушение Single Responsibility Principle (SRP)

Класс UIViewController исторически объединяет множество обязанностей:

  • Управление жизненным циклом представления (viewDidLoad, viewDidAppear).
  • Обработка пользовательских действий и бизнес-логики.
  • Координация переходов и презентации других контроллеров.
  • Часто выступает как Data Source и Delegate для таблиц и коллекций.
class ClassicViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    // Здесь контроллер одновременно:
    // 1. Управляет view
    // 2. Хранит данные (нарушение SRP)
    // 3. Обрабатывает логику таблицы
    
    var data: [String] = []
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return data.count // Логика данных внутри VC
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        cell.textLabel?.text = data[indexPath.row] // Конфигурация ячейки внутри VC
        return cell
    }
    
    @IBAction func buttonTapped(_ sender: Any) {
        // Обработка действий тоже здесь
    }
}

Это приводит к "тяжелым" контроллерам, которые трудно тестировать и поддерживать. Современный подход — разделение на отдельные сущности: ViewModel, Coordinator, отдельные DataSource объекты.

2. UITableViewCell и конфигурация — нарушение Open/Closed Principle (OCP)

Принцип OCP утверждает, что классы должны быть открыты для расширения, но закрыты для модификации. Исторический способ конфигурации ячейки через прямой доступ к свойствам нарушает это:

let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = data.title   // Модификация внутренних свойств
cell.detailTextLabel?.text = data.subtitle
cell.imageView?.image = data.icon

Мы напрямую изменяем внутренние суб-вью (textLabel, imageView), что делает класс "открытым для модификации". Если Apple изменит внутреннюю структуру ячейки, наш код может сломаться. Более современные подходы (как UIContentConfiguration в iOS 14+) пытаются исправить это, предоставляя интерфейс для конфигурации, абстрагированный от внутренней реализации.

3. UIView с обработкой событий — нарушение SRP и смешение абстракций

Кастомные UIView часто становятся "мульти-инструментами":

class CustomButtonView: UIView {
    var title: String = ""
    var action: (() -> Void)? // Сохранение логики действий внутри View
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        action?() // Обработка событий внутри класса отрисовки
        // Это смешивает ответственность: View должна заниматься отрисовкой,
        // а не бизнес-логикой или обработкой действий.
    }
}

Это нарушает SRP и приводит к тесной связности. Правильный подход — использовать систему target-action (UIControl) или делегирование, чтобы отделить логику событий от визуального представления.

4. Массивные Delegate протоколы — нарушение Interface Segregation Principle (ISP)

ISP говорит, что клиенты не должны зависеть от интерфейсов, которые они не используют. Некоторые делегатные протоколы в UIKit предоставляют слишком много методов:

// UIScrollViewDelegate имеет множество методов для разных сценариев
protocol UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView)
    func scrollViewWillBeginDragging(_ scrollView: UIScrollView)
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView)
    // ... и многие другие
}

Если нам нужно только отслеживать скролл (scrollViewDidScroll), мы всё равно должны реализовать протокол, который содержит множество неиспользуемых методов (часто помечаемых как optional в Objective-C, но в Swift это приводит к необходимости реализации всех методов, если протокол не помечен как @objc с optional). Это создает "шум" в коде и нарушает ISP. Идеальным было бы разделение на более мелкие, специализированные протоколы.

5. Нарушение Dependency Inversion Principle (DIP) через прямые зависимости от конкретных классов

DIP предполагает зависимость от абстракций, а не от конкретных реализаций. В UIKit часто встречается жесткая связь:

class OldStyleViewController: UIViewController {
    var tableView: UITableView! // Конкретная зависимость, не абстракция
    var service: ConcreteNetworkService! // Конкретный сервис, не протокол
    
    func fetchData() {
        service.fetchData { [weak self] result in
            self?.tableView.reloadData() // Прямое взаимодействие с конкретной view
        }
    }
}

Это затрудняет замену UITableView на другой компонент списка или замену сервиса для тестирования. Решение — использование протоколов для зависимостей и инжектирование их через инициализатор.

Выводы и современные тенденции

Многие из этих примеров относятся к "классическому" UIKit, использовавшемуся до широкого распространения архитектурных паттернов (MVVM, Coordinator) и акцента на тестируемость. Apple в современных API (SwiftUI, новые части UIKit как UIContentConfiguration, UIAction) стремится к более декларативным и разделенным моделям. Однако при работе с legacy кодом или базовыми компонентами UIKit, разработчику важно самому применять SOLID принципы, оборачивая "тяжелые" классы фреймворка в более чистые абстракции, чтобы минимизировать риски и улучшить поддерживаемость проекта.