Delegate: co to jest, wzorzec delegowania i protokoły iOS

Autor: IT Sectr Opublikowano: 2026-02-17 Czas czytania: 9 min

Delegate — to wzorzec projektowy, w którym jeden obiekt deleguje wykonanie zadania do innego obiektu. W iOS wzorzec jest realizowany przez protokoły Swift i @protocol Objective-C. Delegation to jeden z fundamentalnych wzorców Cocoa Touch, używany w UITableViewDelegate, UITextFieldDelegate i setkach innych API Apple. Według Apple Developer Documentation (2025), około 70% klas systemowych UIKit używa delegatów do dostosowywania zachowania bez dziedziczenia.

Najważniejsze

  • Delegate — wzorzec, w którym obiekt A deleguje wykonanie zadania do obiektu B przez protokół lub interfejs
  • Protokół Swift określa zestaw metod, które delegat może lub musi zaimplementować
  • Słabe referencje (weak) do delegata są obowiązkowe, aby zapobiec retain cycle i wyciekom pamięci
  • UITableViewDelegate — najczęstszy przykład delegata w iOS z 20+ opcjonalnymi metodami
  • Różnica od Observer: delegat to relacja 1:1, podczas gdy powiadomienia działają na zasadzie 1:N

Czym jest wzorzec Delegate?

Delegate — to wzorzec projektowania behawioralnego, pozwalający obiektowi przekazać część swoich obowiązków innemu obiektowi. W przeciwieństwie do dziedziczenia, gdzie klasa potomna nadpisuje metody rodzica, delegat używa kompozycji: obiekt-właściciel przechowuje referencję do delegata i wywołuje jego metody w określonych punktach.

Delegat określa protokół — zestaw metod, które delegat może zaimplementować. Metody dzielą się na obowiązkowe (required) i opcjonalne (optional). W Swift opcjonalne metody protokołu są oznaczane słowem kluczowym @objc optional.

Zasada działania delegata

Obiekt A (właściciel) zawiera właściwość delegate — słabą referencję (weak) do obiektu B (delegat). Gdy następuje zdarzenie, A sprawdza, czy B implementuje odpowiednią metodę protokołu, i wywołuje ją. Słaba referencja jest obowiązkowa: bez niej delegat nie może zostać zwolniony z pamięci, ponieważ właściciel utrzymuje go silną referencją.

Swift
protocol LoaderDelegate: AnyObject {
    func loaderDidStart(_ loader: DataLoader)
    func loader(_ loader: DataLoader, didLoad data: Data)
    func loader(_ loader: DataLoader, didFailWith error: Error)
}

class DataLoader {
    weak var delegate: LoaderDelegate?
    
    func start() {
        delegate?.loaderDidStart(self)
        // ładowanie asynchroniczne
    }
}

Klasa DataLoader definiuje protokół LoaderDelegate i wywołuje metody delegata w kluczowych punktach cyklu życia ładowania. AnyObject gwarantuje, że protokół może być zaimplementowany tylko przez klasy — jest to konieczne dla słabej referencji.

Jak działa Delegate w Swift?

Implementacja delegata w Swift obejmuje trzy kroki: zadeklarowanie protokołu, utworzenie słabej właściwości delegate we właścicielu i implementacja protokołu w klasie-delegacie. Rozważmy na przykładzie niestandardowego UITextField z walidacją.

Swift
protocol ValidatorDelegate: AnyObject {
    func validate(_ input: String) -> Bool
    func validatorDidFail(_ input: String)
}

class ValidatedTextField: UITextField {
    weak var validator: ValidatorDelegate?
    
    override func textDidChange() {
        guard let text = self.text else { return }
        if validator?.validate(text) == false {
            validator?.validatorDidFail(text)
            self.layer.borderColor = UIColor.red.cgColor
        }
    }
}

class LoginViewController: UIViewController, ValidatorDelegate {
    let textField = ValidatedTextField()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        textField.validator = self
    }
    
    func validate(_ input: String) -> Bool {
        return input.count >= 6
    }
    
    func validatorDidFail(_ input: String) {
        print("Walidacja nie powiodła się: zbyt krótkie dane wejściowe")
    }
}

LoginViewController implementuje protokół ValidatorDelegate i ustawia siebie jako delegata textField. Przy każdej zmianie tekstu ValidatedTextField wywołuje validate(_:), a jeśli walidacja się nie powiedzie — validatorDidFail(_:). Controller działa jako pośrednik między View a logiką walidacji.

Delegate vs NotificationCenter vs Closure

Wybór między delegatem, powiadomieniami i domknięciami zależy od liczby odbiorców i stopnia powiązania komponentów. Każdy mechanizm rozwiązuje problem komunikacji między obiektami, ale z różnymi kompromisami.

CechaDelegateNotificationCenterClosure
Typ połączenia1:11:N1:1
PowiazanieSłabe (przez protokół)Bardzo słabe (klucz-string)Średnie (przechwytywanie kontekstu)
Bezpieczeństwo typówPełneBrak (Any?)Pełne
Ryzyko retain cycleNie (weak)NieTak (przechwycenie self)
Kiedy stosowaćZłożone callbacki z wieloma metodamiZdarzenia interesujące wieluProste domknięcia z 1-2 callbackami

Delegate jest optymalny, gdy trzeba przekazać serię powiązanych zdarzeń jednemu odbiorcy. NotificationCenter lepszy do broadcast-owych powiadomień. Closure — do prostych operacji asynchronicznych, takich jak completion handler w URLSession.

Implementacja Delegate w Objective-C

Objective-C używa @protocol i @optional do deklarowania delegatów. W przeciwieństwie do Swift, wszystkie metody protokołu są domyślnie opcjonalne. Kluczowa różnica — wywołanie respondsToSelector: przed wysłaniem wiadomości do delegata, ponieważ metoda może nie być zaimplementowana.

Objective-C
@protocol ImageCacheDelegate 
@optional
- (void)cacheDidStartDownload: (ImageCache *)cache;
- (void)cache: (ImageCache *)cache didCacheImage: (UIImage *)image;
@required
- (void)cache: (ImageCache *)cache didFailWithError: (NSError *)error;
@end

@interface ImageCache : NSObject
@property (nonatomic, weak) id<ImageCacheDelegate> delegate;
- (void)downloadImageAtURL: (NSURL *)url;
@end

@implementation ImageCache
- (void)downloadImageAtURL: (NSURL *)url {
    if ([self.delegate respondsToSelector:@selector(cacheDidStartDownload:)]) {
        [self.delegate cacheDidStartDownload:self];
    }
    // asynchroniczne ładowanie obrazu
}
@end

Kluczowa różnica w Objective-C: przed wywołaniem metody opcjonalnej konieczna jest kontrola respondsToSelector:. W Swift opcjonalne metody protokołu eliminują tę kontrolę — optional chaining (?.) automatycznie obsługuje przypadek braku implementacji.

Typowe błędy przy pracy z delegatami

Błędy używania delegatów prowadzą do wycieków pamięci, crashy aplikacji i nieoczywistych bugów. Rozważmy pięć najczęstszych problemów.

Silna referencja do delegata

Retain cycle — najczęstszy błąd. Jeśli właściwość delegate jest zadeklarowana jako strong, a delegat z kolei posiada obiekt-właściciela, powstaje cykl utrzymania. Oba obiekty nigdy nie zostaną zwolnione z pamięci. Rozwiązanie: zawsze deklarować delegata jako weak var w Swift lub @property (weak) w Objective-C.

Delegat nie wyzerowany po dealloc

Jeśli obiekt-właściciel przeżywa delegata, a referencja pozostała, wywołanie metody delegata doprowadzi do EXC_BAD_ACCESS. Słaba referencja rozwiązuje ten problem automatycznie: po zwolnieniu delegata właściwość staje się nil. Jednak w scenariuszach wielowątkowych warto dodatkowo sprawdzać delegata w głównym wątku.

Nadmierna liczba metod w protokole

Protokół z 20+ metodami narusza zasadę separacji interfejsów (ISP). UITableViewDelegate zawiera około 30 opcjonalnych metod — to historyczny wyjątek. We własnych protokołach lepiej podzielić odpowiedzialność na kilka mniejszych protokołów, każdy z własną rolą.

Przykłady Delegate w systemowych API iOS

Systemowe API Apple aktywnie używają wzorca Delegate. Rozważmy trzy kluczowe przykłady z UIKit, które występują w każdej iOS-owej aplikacji.

APIProtokółKluczowe metody
UITableViewUITableViewDelegatedidSelectRowAt, heightForRowAt, willDisplay
UITextFieldUITextFieldDelegateshouldChangeCharactersIn, didBeginEditing, shouldReturn
URLSessionURLSessionDelegatedidReceiveChallenge, didCompleteWithError, didBecomeInvalidWithError

Każdy z tych protokołów realizuje różne aspekty zachowania: UITableViewDelegate zarządza wyglądem i reakcją na dotknięcia, UITextFieldDelegate kontroluje wprowadzanie tekstu, URLSessionDelegate obsługuje zdarzenia sieciowe. To demonstruje elastyczność wzorca: delegat może być dostosowany do dowolnego obszaru odpowiedzialności.

Często zadawane pytania

Czym różni się delegate od dataSource w iOS?

Delegate zarządza zachowaniem i wyglądem (wysokość komórki, reakcja na dotknięcie). DataSource dostarcza dane (liczba wierszy, zawartość komórek). W UITableViewDelegate i UITableViewDataSource — to dwa oddzielne protokoły, dzielące odpowiedzialność za prezentację i dane.

Dlaczego delegate jest deklarowany jako weak?

Słaba referencja (weak) zapobiega retain cycle. Właściciel (np. UITableView) utrzymuje referencję do delegata tylko jako weak. Jeśli delegat (UIViewController) posiada tabelę, silna referencja do delegata utworzyłaby cykl: ViewController → UITableView → Delegate (ViewController). Weak przerywa ten cykl.

Czy można używać delegate w SwiftUI?

W SwiftUI wzorzec Delegate jest używany rzadziej — zastępują go @Binding, @State i domknięcia. Jednak delegate nadal jest stosowany do integracji UIKit przez UIViewRepresentable. Na przykład MKMapViewDelegate i WKUIDelegate pozostają aktualne przy opakowywaniu komponentów UIKit w SwiftUI.

Czym jest @objc optional w protokołach Swift?

@objc optional pozwala zadeklarować opcjonalne metody w protokole Swift. To mechanizm kompatybilności z Objective-C runtime. Bez @objc wszystkie metody protokołu Swift są domyślnie obowiązkowe. Optional jest używany w protokołach UIKit, gdzie delegat może zaimplementować tylko potrzebne mu metody.

Ilu delegatów może mieć jeden obiekt?

Jeden obiekt może mieć tylko jednego delegata dla każdej właściwości delegate. Jeśli trzeba powiadomić wiele obiektów, użyj multicast delegate, tablicy delegatów lub NotificationCenter. Wzorzec Delegate został zaprojektowany jako relacja 1:1.

Podsumowanie

  • Delegate — wzorzec projektowania behawioralnego, przekazujący wykonanie zadania z jednego obiektu do drugiego przez protokół
  • Protokół Swift deklaruje zestaw metod; opcjonalne metody oznacza się @objc optional, obowiązkowe są zawsze implementowane
  • Słaba referencja (weak) do delegata jest obowiązkowa, aby zapobiec retain cycle i wyciekom pamięci
  • UITableViewDelegate — najbardziej znany przykład; jego metody zarządzają wysokością, zaznaczeniem i wyświetlaniem komórek
  • Delegate różni się od NotificationCenter relacją 1:1 wobec 1:N i pełnym bezpieczeństwem typów
  • Objective-C wymaga sprawdzenia respondsToSelector: dla metod opcjonalnych, Swift obsługuje to automatycznie
  • Zalecenie: używaj delegate do złożonych callbacków z wieloma powiązanymi metodami i closure do prostych domknięć

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ż