Delegate: co to jest, wzorzec delegowania i jak działa na iOS

Autor: IT Sectr Opublikowano: 2026-03-17 Czas czytania: 10 min

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 — wzorzec, w którym obiekt powierza obsługę zdarzeń innemu obiektowi przez protokół.
  • Protocol w Swift określa zestaw metod, które delegate może lub musi zaimplementować.
  • Weak reference jest obowiązkowa dla właściwości delegate, aby uniknąć retain cycle.
  • @objc optional umożliwia uczynienie metod protokołu opcjonalnymi do implementacji.
  • URLSessionDelegate — asynchroniczny delegate do obsługi zdarzeń zapytania sieciowego.

Czym jest Delegate?

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.

Definiowanie protokołu Delegate

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.

swift
// 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()
    }
}

Słaba referencja do delegate

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.

swift
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)
    }
}

Jak działa wzorzec Delegate w iOS?

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.

Cykl życia delegata

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.

Asynchroniczne powiadamianie przez Delegate

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ń.

swift
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 vs Callback: porównanie podejść

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ń.

Kiedy Delegate wygrywa

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).

Kiedy Callback wygrywa

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.

Wbudowane Delegaty w iOS SDK

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

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

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ń.

DelegateMetodPrzeznaczenie
UITableViewDelegate25Wygląd i interakcja z tabelą
UITextFieldDelegate8Obsługa wprowadzania tekstu i klawiatury
CLLocationManagerDelegate12Aktualizacja lokalizacji i strefy geograficzne
URLSessionDelegate6Zarządzanie sesją HTTP i certyfikatami
UNUserNotificationCenterDelegate4Obsługa push-notyfikacji na pierwszym planie

Zarządzanie pamięcią przy pracy z Delegate

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 przez Delegate

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.

swift
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
        )
    }
}

Bezpieczne sprawdzanie delegate przed wywołaniem

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.

Typowe błędy przy implementacji Delegate

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.

Strong reference zamiast weak

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.

Ustawienie delegate po uruchomieniu operacji

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

Dlaczego delegate jest deklarowany jako weak?

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.

Jaka jest różnica między delegate a dataSource?

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.

Czy można użyć struct jako delegate?

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.

Czym jest metoda responds(to:) i do czego służy?

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.

Czy Delegate to singleton?

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

  • Delegate — wzorzec, w którym obiekt deleguje obsługę zdarzeń do innego obiektu przez protokół z typowanymi metodami.
  • Weak var jest obowiązkowy dla właściwości delegate, aby zapobiec retain cycle i wyciekom pamięci.
  • Protokół określa obowiązkowe i opcjonalne (@objc optional) metody delegowania.
  • iOS SDK zawiera 15+ wbudowanych protokołów delegate: UITableViewDelegate, URLSessionDelegate, CLLocationManagerDelegate.
  • Delegate jest preferowany nad callback przy 3+ różnych zdarzeniach z jednego źródła (CLLocationManager).
  • Asynchroniczny delegate jest używany w URLSession do pobierania danych i postępu bez blokowania wątku.
  • Ustawiaj delegate przed uruchomieniem operacji asynchronicznej i zeruj w deinit dla bezpiecznej pracy z pamięcią.

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.

Omów projekt

Przeczytaj również