Delegate — to wzorzec projektowy, w którym jeden obiekt deleguje wykonywanie zadań do innego obiektu za pomocą protokołu z predefiniowanymi metodami. W iOS development Delegate jest jednym z fundamentalnych wzorców Cocoa Touch, używanym do asynchronicznego powiadamiania bez bezpośredniego połączenia między nadawcą a odbiorcą. Według Apple Documentation (2025), delegowanie jest stosowane w Foundation i UIKit do obsługi zdarzeń tabel, zapytań sieciowych i zarządzania lokalizacją. Wzorzec zapewnia słabe powiązanie komponentów i ponowne wykorzystanie kodu.
Najważniejsze
Delegate (delegat) — to obiekt, który implementuje określony protokół i otrzymuje powiadomienia o zdarzeniach innego obiektu. Wzorzec Delegation jest alternatywą dla dziedziczenia: zamiast tworzenia podklasy w celu nadpisania metod, obiekt deleguje obsługę zdarzeń do zewnętrznego obiektu. W iOS delegation jest zaimplementowany przez protokoły Swift z obowiązkowymi i opcjonalnymi metodami. Właściwość delegate jest zawsze deklarowana jako weak var, aby uniknąć cyklicznych referencji między obiektami.
Protokół delegate określa kontrakt interakcji między obiektami. Obowiązkowe metody muszą być zaimplementowane przez delegata, w przeciwnym razie kod się nie skompiluje. Opcjonalne metody są oznaczane atrybutem @objc optional i pozwalają delegatowi reagować tylko na potrzebne zdarzenia. Nazwy metod są zgodne z konwencją: pierwszy parametr — obiekt-wysyłający, drugi — dane zdarzenia. Na przykład tableView(_:didSelectRowAt:) wskazuje, że nadawcą jest UITableView, a dane to indeks wybranego wiersza.
// Protokół 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)
}
// Klasa używająca 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()
}
}
Właściwość delegate musi być zadeklarowana jako weak var (słaby wskaźnik), aby zapobiec retain cycle. Gdyby referencja była silna, delegat i delegujący obiekt trzymałyby się nawzajem, a ARC nie mógłby zwolnić ich pamięci. Protokoły delegate dziedziczą AnyObject (tylko dla klas), co umożliwia użycie weak. Struktury i enum nie mogą być delegatami ze względu na semantykę wartości. Alternatywą dla value types są callback closures.
class ViewController: DownloadManagerDelegate {
let manager = DownloadManager()
override func viewDidLoad() {
super.viewDidLoad()
manager.delegate = self // weak — brak retain cycle
manager.startDownload(from: url)
}
func downloadManager(_ manager: DownloadManager, didFinishWith data: Data) {
processData(data)
}
func downloadManager(_ manager: DownloadManager, didFailWith error: Error) {
showError(error)
}
}
Wzorzec Delegate działa na zasadzie „jeden-do-jednego”: jeden obiekt-wysyłający może mieć tylko jednego delegata w danym momencie. Gdy występuje zdarzenie, nadawca sprawdza, czy delegate jest ustawiony, i wywołuje odpowiednią metodę protokołu. Zaleta w porównaniu z bezpośrednim wywołaniem — nadawca nie zna typu delegata, tylko to, że spełnia on protokół. Jest to zgodne z zasadą odwrócenia zależności (DIP) z SOLID.
Delegat jest przypisywany przez przypisanie: someObject.delegate = self. Przy dealokacji delegata właściwość automatycznie staje się nil ze względu na semantykę weak. Przed wywołaniem metody delegate sprawdza się przez optional chaining: delegate?.method(). Jeśli delegate jest nil — wywołanie jest ignorowane bez crasha. Dla opcjonalnych metod protokołu stosuje się dodatkowe sprawdzenie: delegate?.responds(to: #selector(...)), chociaż w Swift to sprawdzenie jest zwykle niejawne przez deklarację optional method.
W środowisku wielowątkowym delegate jest używany do asynchronicznego zwracania wyniku. URLSession udostępnia URLSessionDelegate z metodami wywoływanymi przy otrzymaniu danych, timeoutzie lub błędzie uwierzytelniania. Metody delegate są wykonywane w tle kolejki URLSession, dlatego wymagana jest dyspozycja na main queue do aktualizacji UI. Asynchroniczny delegate nie blokuje wątku wywołującego, umożliwiając kontynuację innych zadań.
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 i Callback rozwiązują to samo zadanie — asynchroniczne powiadamianie — ale na różne sposoby. Delegate używa protokołu z nazwanymi metodami, callback — domknięcia z przechwytywaniem kontekstu. Wybór zależy od liczby zdarzeń, złożoności sygnatur i preferencji architektonicznych. Apple zaleca delegate dla API z wieloma zdarzeniami (UITableView — 20+ metod) i callback dla jednorazowych zakończeń.
Delegate jest preferowany, gdy trzeba obsłużyć kilka różnych zdarzeń z jednego źródła. Na przykład CLLocationManager powiadamia delegata o zmianie lokalizacji, błędach uprawnień, wejściu/wyjściu ze stref geograficznych i zmianie statusu usług. Każde zdarzenie to osobna metoda protokołu z czytelną nazwą i typowanymi parametrami. Delegate jest również wygodny do konfiguracji zachowania (metody should, will, did).
Callback jest prostszy dla jednorazowych zapytań z jednym wynikiem. Completion handler w URLSession.dataTask zajmuje jeden wiersz w punkcie wywołania wobec co najmniej trzech metod protokołu. Callback jest też bardziej naturalny dla łańcuchów funkcyjnych (map, flatMap, async/await). Jednak przy zagnieżdżeniu powyżej 2-3 poziomów callback zamienia się w Callback Hell, podczas gdy delegate zawsze pozostaje płaski.
iOS SDK zawiera dziesiątki wbudowanych protokołów delegate dla różnych podsystemów. Każdy z nich jest zaprojektowany dla konkretnego scenariusza interakcji. Według Apple Documentation (2025), najczęściej używane delegate to UITableViewDelegate, UITextFieldDelegate, CLLocationManagerDelegate, URLSessionDelegate i UNUserNotificationCenterDelegate. Te protokoły zawierają od 3 do 30 metod o różnym stopniu obowiązkowości.
UITableViewDelegate zarządza wyglądem i zachowaniem komórek tabeli. Zawiera metody do obsługi zaznaczania wierszy, ustawiania wysokości komórek, niestandardowych widoków header/footer i akcji przesunięcia. Wszystkie metody protokołu są opcjonalne, co pozwala zaimplementować tylko potrzebną funkcjonalność. W przypadku braku delegata tabela działa z ustawieniami domyślnymi. Historycznie delegate łączył się z UITableViewDataSource.
URLSessionDelegate zapewnia szczegółową kontrolę nad zapytaniami HTTP. Metody delegata są wywoływane przy otrzymaniu odpowiedzi serwera, danych, zakończeniu pobierania. Wyspecjalizowane podprotokoły URLSessionTaskDelegate i URLSessionDataDelegate rozszerzają podstawową funkcjonalność dla konkretnych typów zadań. Delegate jest wymagany do obsługi pobierania w tle, certyfikatów SSL i niestandardowego przetwarzania przekierowań.
| Delegate | Metod | Przeznaczenie |
|---|---|---|
| UITableViewDelegate | 25 | Wygląd i interakcja z tabelą |
| UITextFieldDelegate | 8 | Obsługa wprowadzania tekstu i klawiatury |
| CLLocationManagerDelegate | 12 | Aktualizacja lokalizacji i strefy geograficzne |
| URLSessionDelegate | 6 | Zarządzanie sesją HTTP i certyfikatami |
| UNUserNotificationCenterDelegate | 4 | Obsługa push-notyfikacji na pierwszym planie |
Zarządzanie pamięcią to krytyczny aspekt pracy z delegate w iOS. ARC (Automatic Reference Counting) automatycznie zarządza pamięcią, ale tylko przy prawidłowym użyciu referencji weak/unowned. Naruszenie zasad prowadzi do wycieków pamięci lub przedwczesnej dealokacji. Delegate zadeklarowany jako strong tworzy retain cycle, jeśli właściciel delegata również przechowuje referencję do delegującego obiektu.
Retain cycle powstaje, gdy obiekt A (właściciel) ustawia siebie jako delegata obiektu B, a B przechowuje silną referencję do delegate. Przykład: ViewController tworzy URLSession, ustawia self jako delegata sesji, ale URLSession domyślnie przechowuje silną referencję do delegate, jeśli nie określono delegateQueue. Rozwiązanie — zawsze sprawdzać dokumentację API pod kątem typu referencji do delegate (weak lub strong) i jawnie zerować delegate w deinit.
class SafeViewController: UIViewController {
private var session: URLSession?
private var service: NetworkService?
override func viewDidLoad() {
super.viewDidLoad()
service = NetworkService()
service?.delegate = self
}
deinit {
// Zerowanie delegate w deinit — best practice
service?.delegate = nil
session?.invalidateAndCancel()
}
}
// URLSession z weak delegate przez 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
)
}
}
Przed wywołaniem metody delegata należy sprawdzić, czy delegat istnieje (nie nil) i czy implementuje wywoływaną metodę. Dla obowiązkowych metod protokołu sprawdzenie nie jest wymagane — kompilator gwarantuje implementację. Dla opcjonalnych metod używaj respond(to:) lub optional chaining. Jeśli delegat został dealokowany, weak referencja automatycznie staje się nil, a wywołanie delegata jest ignorowane. Jest to bezpieczne zachowanie, które nie wymaga dodatkowej obsługi.
Programiści często popełniają błędy przy pracy z wzorcem Delegate, szczególnie na początku nauki iOS. Najczęstsze: retain cycle z powodu strong delegate, zapomnienie wywołania delegate?.method(), nieprawidłowa sygnatura metod protokołu, ustawienie delegate po rozpoczęciu operacji i kolizje wielowątkowe. Omówmy każdy błąd i sposoby jego zapobiegania.
Najbardziej krytyczny błąd — zadeklarowanie właściwości delegate jako strong var zamiast weak var. Tworzy to retain cycle, w którym ani delegat, ani delegujący obiekt nie mogą zostać zwolnione. Konsekwencje: wyciek pamięci, spowolnienie aplikacji i ukryte błędy. Rozwiązanie: zawsze używaj weak var dla delegate, a protokół dziedzicz po AnyObject, aby wykluczyć użycie value types jako delegata.
Jeśli delegate jest ustawiony po wywołaniu metody asynchronicznej, pierwsze zdarzenia mogą zostać utracone. Przykład: wywołanie startDownload() przed przypisaniem manager.delegate = self prowadzi do utraty callbacka o zakończeniu, jeśli pobieranie jest wykonywane synchronicznie lub bardzo szybko. Rozwiązanie: ustawiaj delegate przed wywołaniem metody asynchronicznej i dokumentuj kolejność inicjalizacji w komentarzach do protokołu.
Często zadawane pytania
Weak zapobiega retain cycle między delegatem a delegującym obiektem. Gdyby referencja była strong, obiekty trzymałyby się nawzajem, a ARC nie mógłby ich zwolnić. Weak-referencja automatycznie staje się nil przy dealokacji delegata. Jest to standardowa praktyka Cocoa Touch od czasów Objective-C i została zachowana w Swift dla wstecznej kompatybilności.
Delegate obsługuje zdarzenia i zarządza zachowaniem (wysokość komórek, reakcja na kliknięcia). DataSource dostarcza dane do wyświetlenia (liczba wierszy, komórki). Delegate odpowiada na pytanie „jak?", dataSource — na pytanie „co?". W iOS oba są implementowane przez protokoły, często w tym samym kontrolerze, ale koncepcyjnie są rozdzielone.
Nie, jeśli protokół dziedziczy AnyObject (protokół klasowy). Weak-referencje są dostępne tylko dla reference types (klas). Dla value types (struct, enum) używaj callback-domknięć lub osobnej klasy-opakowania. Jeśli kontrolujesz protokół, możesz nie dziedziczyć AnyObject, ale wtedy weak jest zabroniony — wybieraj świadomie między weak-delegatem a struct-delegatem.
responds(to:) — metoda NSObjectProtocol, która sprawdza, czy obiekt implementuje wskazany selektor. Jest używana do sprawdzania opcjonalnych metod @objc protokołu przed wywołaniem. Bez tego sprawdzenia wywołanie niezaimplementowanej opcjonalnej metody spowoduje NSInvalidArgumentException. W Swift dla protokołów z @objc optional sprawdzenie może być niejawne przez optional binding.
Nie, delegate to wzorzec delegowania, a nie singleton. W przeciwieństwie do singletona, delegat może być zastąpiony w runtime i istnieje w jednej instancji dla każdego delegującego obiektu. Jeden obiekt może być delegatem dla wielu nadawców. Singleton to wzorzec kreacyjny gwarantujący pojedynczą instancję klasy, co nie ma związku z delegowaniem.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również