Delegate — это паттерн проектирования, при котором один объект делегирует выполнение задач другому объекту через протокол с заранее определёнными методами. В iOS разработке Delegate является одним из фундаментальных паттернов Cocoa Touch, используемым для асинхронного уведомления без прямой связи между отправителем и получателем. По данным Apple Documentation (2025), делегирование применяется в Foundation и UIKit для обработки событий таблиц, сетевых запросов и управления местоположением. Паттерн обеспечивает слабую связанность компонентов и переиспользование кода.
Главное
Delegate (делегат) — это объект, который реализует определённый протокол и получает уведомления о событиях другого объекта. Паттерн Delegation является альтернативой наследованию: вместо создания подкласса для переопределения методов, объект делегирует обработку событий внешнему объекту. В iOS delegation реализован через протоколы Swift с обязательными и опциональными методами. Свойство delegate всегда объявляется как weak var, чтобы избежать циклических ссылок между объектами.
Протокол delegate определяет контракт взаимодействия между объектами. Обязательные методы должны быть реализованы делегатом, иначе код не скомпилируется. Опциональные методы помечаются атрибутом @objc optional и позволяют делегату реагировать только на нужные события. Имена методов следуют конвенции: первый параметр — объект-отправитель, второй — данные события. Например, tableView(_:didSelectRowAt:) указывает, что отправитель — UITableView, а данные — индекс выбранной строки.
// Протокол Delegate
protocol DownloadManagerDelegate: AnyObject {
func downloadManager(_ manager: DownloadManager,
didFinishWith data: Data)
func downloadManager(_ manager: DownloadManager,
didFailWith error: Error)
@objc optional func downloadManager(_ manager: DownloadManager,
didUpdateProgress progress: Float)
}
// Класс, использующий Delegate
class DownloadManager {
weak var delegate: DownloadManagerDelegate?
func startDownload(from url: URL) {
URLSession.shared.dataTask(with: url) { [weak self] data, _, error in
guard let self else { return }
if let error = error {
self.delegate?.downloadManager(self, didFailWith: error)
} else if let data = data {
self.delegate?.downloadManager(self, didFinishWith: data)
}
}.resume()
}
}
Свойство delegate обязательно объявляется как weak var (слабый указатель) для предотвращения retain cycle. Если бы ссылка была сильной, делегат и делегирующий объект держали бы друг друга, и ARC не смог бы освободить их память. Протоколы delegate наследуют AnyObject (только для классов), что позволяет использовать weak. Структуры и enum не могут быть делегатами из-за семантики значений. Альтернатива для value types — callback closures.
class ViewController: DownloadManagerDelegate {
let manager = DownloadManager()
override func viewDidLoad() {
super.viewDidLoad()
manager.delegate = self // weak — нет retain cycle
manager.startDownload(from: url)
}
func downloadManager(_ manager: DownloadManager, didFinishWith data: Data) {
processData(data)
}
func downloadManager(_ manager: DownloadManager, didFailWith error: Error) {
showError(error)
}
}
Паттерн Delegate работает по принципу «один-к-одному»: один объект-отправитель может иметь только одного делегата в конкретный момент времени. Когда происходит событие, отправитель проверяет, установлен ли delegate, и вызывает соответствующий метод протокола. Преимущество перед прямым вызовом — отправитель не знает тип делегата, только то, что он соответствует протоколу. Это соблюдает принцип инверсии зависимостей (DIP) из SOLID.
Делегат назначается через присваивание: someObject.delegate = self. При деаллокации делегата свойство автоматически становится nil из-за weak semantics. Перед вызовом метода delegate проверяется через optional chaining: delegate?.method(). Если delegate nil — вызов игнорируется без краша. Для опциональных методов протокола используется дополнительная проверка: delegate?.responds(to: #selector(...)), хотя в Swift эта проверка обычно неявная через optional method declaration.
В многопоточной среде delegate используется для асинхронного возврата результата. URLSession предоставляет URLSessionDelegate с методами, вызываемыми при получении данных, таймауте или ошибке аутентификации. Delegate-методы выполняются в фоновой очереди URLSession, поэтому требуется диспетчеризация на main queue для UI-обновлений. Асинхронный delegate не блокирует вызывающий поток, позволяя продолжить выполнение других задач.
class NetworkService: NSObject, URLSessionDataDelegate {
private lazy var session = URLSession(
configuration: .default,
delegate: self,
delegateQueue: OperationQueue()
)
private var receivedData = Data()
func urlSession(_ session: URLSession,
dataTask: URLSessionDataTask,
didReceive data: Data) {
receivedData.append(data)
let progress = Float(receivedData.count) / Float(expectedSize)
DispatchQueue.main.async {
self.progressHandler?(progress)
}
}
func urlSession(_ session: URLSession,
task: URLSessionTask,
didCompleteWithError error: Error?) {
if let error = error {
delegate?.networkService(self, didFailWith: error)
} else {
delegate?.networkService(self, didReceive: receivedData)
}
}
}
Delegate и Callback решают одну задачу — асинхронное уведомление, — но разными способами. Delegate использует протокол с именованными методами, callback — замыкание с захватом контекста. Выбор зависит от количества событий, сложности сигнатур и архитектурных предпочтений. Apple рекомендует delegate для API с множеством событий (UITableView — 20+ методов) и callback для разовых завершений.
Delegate предпочтительнее, когда нужно обрабатывать несколько различных событий от одного источника. Например, CLLocationManager уведомляет делегата об изменении местоположения, ошибках разрешений, входе/выходе из геозон и изменении статуса служб. Каждое событие — отдельный метод протокола с понятным именем и типизированными параметрами. Delegate также удобен для конфигурации поведения (should, will, did методы).
Callback проще для разовых запросов с одним результатом. Completion handler в URLSession.dataTask занимает одну строку в точке вызова против минимум трёх методов протокола. Callback также естественнее для функциональных цепочек (map, flatMap, async/await). Однако при вложенности более 2-3 уровней callback превращается в Callback Hell, тогда как delegate всегда остаётся плоским.
iOS SDK содержит десятки встроенных delegate-протоколов для различных подсистем. Каждый из них спроектирован для конкретного сценария взаимодействия. По данным Apple Documentation (2025), наиболее используемые delegate — UITableViewDelegate, UITextFieldDelegate, CLLocationManagerDelegate, URLSessionDelegate и UNUserNotificationCenterDelegate. Эти протоколы содержат от 3 до 30 методов с различной обязательностью.
UITableViewDelegate управляет внешним видом и поведением ячеек таблицы. Он содержит методы для обработки выделения строк, настройки высоты ячеек, кастомных представлений header/footer и свайп-действий. Все методы протокола опциональны, что позволяет реализовать только нужную функциональность. При отсутствии делегата таблица работает с настройками по умолчанию. Исторически delegate сочетался с UITableViewDataSource.
URLSessionDelegate обеспечивает детальный контроль над HTTP-запросами. Методы делегата вызываются при получении ответа сервера, данных, завершении загрузки. Специализированные подпротоколы URLSessionTaskDelegate и URLSessionDataDelegate расширяют базовый функционал для конкретных типов задач. Delegate требуется для поддержки фоновых загрузок, сертификатов SSL и кастомной обработки редиректов.
| Delegate | Методов | Назначение |
|---|---|---|
| UITableViewDelegate | 25 | Внешний вид и взаимодействие с таблицей |
| UITextFieldDelegate | 8 | Обработка ввода текста и клавиатуры |
| CLLocationManagerDelegate | 12 | Обновление местоположения и геозоны |
| URLSessionDelegate | 6 | Управление HTTP-сессией и сертификатами |
| UNUserNotificationCenterDelegate | 4 | Обработка push-уведомлений на переднем плане |
Управление памятью — критический аспект работы с delegate в iOS. ARC (Automatic Reference Counting) автоматически управляет памятью, но только при правильном использовании weak/unowned ссылок. Нарушение правил приводит к утечкам памяти или преждевременной деаллокации. Delegate, объявленный как strong, создаёт retain cycle, если владелец делегата также хранит ссылку на делегирующий объект.
Retain cycle возникает, когда объект A (владелец) устанавливает себя делегатом объекта B, а B хранит сильную ссылку на delegate. Пример: ViewController создаёт URLSession, устанавливает self делегатом сессии, но URLSession по умолчанию хранит сильную ссылку на delegate, если не указан delegateQueue. Решение — всегда проверять документацию API на тип ссылки на delegate (weak или strong) и явно обнулять delegate в deinit.
class SafeViewController: UIViewController {
private var session: URLSession?
private var service: NetworkService?
override func viewDidLoad() {
super.viewDidLoad()
service = NetworkService()
service?.delegate = self
}
deinit {
// Обнуление delegate в deinit — best practice
service?.delegate = nil
session?.invalidateAndCancel()
}
}
// URLSession с weak delegate через NSObject
class WeakDelegateSession: NSObject {
private weak var delegate: URLSessionDelegate?
func createSession() -> URLSession {
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
return URLSession(
configuration: .default,
delegate: self,
delegateQueue: queue
)
}
}
Перед вызовом метода делегата необходимо проверить, что делегат существует (не nil) и реализует вызываемый метод. Для обязательных методов протокола проверка не требуется — компилятор гарантирует реализацию. Для опциональных методов используйте respond(to:) или optional chaining. Если delegate деаллоцирован, weak ссылка автоматически становится nil, и вызов делегата игнорируется. Это безопасное поведение, не требующее дополнительной обработки.
Разработчики часто допускают ошибки при работе с паттерном Delegate, особенно на начальном этапе освоения iOS. Наиболее распространённые: retain cycle из-за strong delegate, забыли вызвать delegate?.method(), неправильная сигнатура методов протокола, установка delegate после начала операции и многопоточные коллизии. Рассмотрим каждую ошибку и способы её предотвращения.
Самая критичная ошибка — объявление свойства delegate как strong var вместо weak var. Это создаёт retain cycle, при котором ни делегат, ни делегирующий объект не могут быть освобождены. Последствия: утечка памяти, замедление приложения и скрытые баги. Решение: всегда используйте weak var для delegate, а протокол наследуйте от AnyObject, чтобы исключить использование value types в качестве делегата.
Если делегат устанавливается после вызова асинхронного метода, первые события могут быть потеряны. Пример: вызов startDownload() до присвоения manager.delegate = self приводит к потере callback о завершении, если загрузка выполняется синхронно или очень быстро. Решение: устанавливайте delegate перед вызовом асинхронного метода и документируйте порядок инициализации в комментариях к протоколу.
Часто задаваемые вопросы
Weak предотвращает retain cycle между делегатом и делегирующим объектом. Если бы ссылка была strong, объекты держали бы друг друга, и ARC не смог бы их освободить. Weak-ссылка автоматически становится nil при деаллокации делегата. Это стандартная практика Cocoa Touch с момента появления Objective-C и сохранена в Swift для обратной совместимости.
Delegate обрабатывает события и управляет поведением (высота ячеек, реакция на нажатия). DataSource предоставляет данные для отображения (количество строк, ячейки). Делегат отвечает на вопрос «как?», dataSource — на вопрос «что?». В iOS оба реализуются через протоколы, часто в одном контроллере, но концептуально разделены.
Нет, если протокол наследует AnyObject (классовый протокол). weak-ссылки доступны только для reference types (классов). Для value types (struct, enum) используйте callback-замыкания или отдельный класс-обёртку. Если вы контролируете протокол, можно не наследовать AnyObject, но тогда weak запрещён — выбирайте между weak-делегатом и struct-делегатом сознательно.
responds(to:) — метод NSObjectProtocol, проверяющий, реализует ли объект указанный селектор. Используется для проверки опциональных методов @objc протокола перед вызовом. Без этой проверки вызов нереализованного опционального метода приведёт к NSInvalidArgumentException. В Swift для протоколов с @objc optional проверка может быть неявной через optional binding.
Нет, delegate — это паттерн делегирования, а не синглтон. В отличие от синглтона, делегат может быть заменён в рантайме и существует в единственном экземпляре для каждого делегирующего объекта. Один объект может быть делегатом для нескольких отправителей. Синглтон — это порождающий паттерн, гарантирующий единственный экземпляр класса, что не имеет отношения к делегированию.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также