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 — 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.
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ą.
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.
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ą.
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.
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.
| Cecha | Delegate | NotificationCenter | Closure |
|---|---|---|---|
| Typ połączenia | 1:1 | 1:N | 1:1 |
| Powiazanie | Słabe (przez protokół) | Bardzo słabe (klucz-string) | Średnie (przechwytywanie kontekstu) |
| Bezpieczeństwo typów | Pełne | Brak (Any?) | Pełne |
| Ryzyko retain cycle | Nie (weak) | Nie | Tak (przechwycenie self) |
| Kiedy stosować | Złożone callbacki z wieloma metodami | Zdarzenia interesujące wielu | Proste 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.
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.
@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
}
@endKluczowa 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.
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.
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.
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.
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ą.
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.
| API | Protokół | Kluczowe metody |
|---|---|---|
| UITableView | UITableViewDelegate | didSelectRowAt, heightForRowAt, willDisplay |
| UITextField | UITextFieldDelegate | shouldChangeCharactersIn, didBeginEditing, shouldReturn |
| URLSession | URLSessionDelegate | didReceiveChallenge, 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
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.
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.
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.
@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.
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
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ż