Что такое Delegate? Как его использовать?ios-58

Что такое делегат?

Делегат — это паттерн проектирования, который позволяет одному объекту (делегату) реагировать на события или выполнять задачи от имени другого объекта. Это реализация принципа инверсии зависимостей (D из SOLID).

Основные компоненты паттерна:

  1. Протокол делегата - определяет контракт (какие методы должен реализовать делегат)
  2. Слабая ссылка на делегат во владеющем объекте (чтобы избежать retain cycle)
  3. Вызов методов делегата в ответ на события

Базовый пример реализации:

// 1. Определяем протокол делегата
protocol DataLoaderDelegate: AnyObject {
    func dataDidLoad(_ data: [String])
    func dataDidFail(with error: Error)
}

class DataLoader {
    // 2. Объявляем слабую ссылку на делегат
    weak var delegate: DataLoaderDelegate?

    func loadData() {
        // Имитация загрузки данных
        DispatchQueue.global().async {
            let success = Bool.random()

            DispatchQueue.main.async {
                if success {
                    let data = ["Item1", "Item2", "Item3"]
                    // 3. Вызываем метод делегата при успехе
                    self.delegate?.dataDidLoad(data)
                } else {
                    let error = NSError(domain: "com.example.error", code: 500)
                    // 3. Вызываем метод делегата при ошибке
                    self.delegate?.dataDidFail(with: error)
                }
            }
        }
    }
}

class ViewController: UIViewController {
    let loader = DataLoader()

    override func viewDidLoad() {
        super.viewDidLoad()
        // 4. Устанавливаем делегат
        loader.delegate = self
        loader.loadData()
    }
}

// 5. Реализуем протокол делегата
extension ViewController: DataLoaderDelegate {
    func dataDidLoad(_ data: [String]) {
        print("Данные загружены: \(data)")
        // Обновляем UI
    }

    func dataDidFail(with error: Error) {
        print("Ошибка загрузки: \(error.localizedDescription)")
        // Показываем ошибку
    }
}

Ключевые особенности делегатов в iOS:

  1. Синтаксис weak var delegate:

    • Обязательно weak для предотвращения retain cycle
    • Протокол должен быть классовым (AnyObject)
  2. Опциональные методы:

@objc protocol CustomTableViewDelegate: AnyObject {
    @objc optional func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell)
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
}
  1. Именование методов:
    • Принято начинать с имени объекта-отправителя
    • Первый параметр — обычно сам отправитель

Где используется в iOS SDK?

  1. UIKit компоненты:

    • UITableViewDelegate, UICollectionViewDelegate
    • UITextFieldDelegate, UIScrollViewDelegate
  2. Системные сервисы:

    • CLLocationManagerDelegate
    • URLSessionTaskDelegate
  3. Жизненный цикл:

    • UIApplicationDelegate
    • UINavigationControllerDelegate

Плюсы делегатов:

✅ Четкое разделение ответственности
✅ Типобезопасность (благодаря протоколам)
✅ Поддержка опциональных методов
✅ Понятный стек вызовов

Минусы делегатов:

❌ Может привести к Massive View Controller
❌ Один делегат на объект (в отличие от нотификаций)
❌ Сложнее для наблюдения за множеством объектов

Альтернативы делегатам:

  1. Closures (замыкания):
class DataLoader {
    var onDataLoaded: (([String]) -> Void)?
    var onError: ((Error) -> Void)?

    func loadData() {
        // ...
        onDataLoaded?(data)
    }
}

// Использование:
loader.onDataLoaded = { data in
    print(data)
}
  1. Combine/RxSwift:
class DataLoader {
    let dataSubject = PassthroughSubject<[String], Error>()

    func loadData() {
        // ...
        dataSubject.send(data)
    }
}

// Подписка:
loader.dataSubject
    .sink(receiveCompletion: { /* ошибка */ },
          receiveValue: { /* данные */ })
    .store(in: &cancellables)
  1. NotificationCenter (для широковещательных событий)

Советы по использованию делегатов:

  1. Всегда делайте делегаты weak
  2. Используйте понятные имена методов
  3. Документируйте обязательные/опциональные методы
  4. Для сложных сценариев комбинируйте с другими паттернами

Пример из реальной практики :

class MyViewController: UIViewController {
    @IBOutlet weak var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.delegate = self
        tableView.dataSource = self
    }
}

extension MyViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        // Обработка нажатия
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 60.0
    }
}

Резюмируем

Делегаты — это фундаментальный паттерн iOS разработки, который:

  • Обеспечивает четкую коммуникацию между объектами
  • Позволяет разделять ответственность
  • Широко используется в Apple фреймворках

Для простых задач можно использовать замыкания, для сложных — комбинировать делегаты с другими паттернами. Главное — соблюдать правила (weak делегаты, понятные протоколы) и использовать паттерн там, где он действительно уместен.

Помните: "Если ваш ViewController реализует 10+ протоколов делегатов — возможно, стоит пересмотреть архитектуру."