Приведи примеры UIKit классов которые противоречат SOLID
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
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 принципы, оборачивая "тяжелые" классы фреймворка в более чистые абстракции, чтобы минимизировать риски и улучшить поддерживаемость проекта.