Weak Reference (słaba referencja) — to referencja do obiektu, która nie zwiększa jego licznika zatrzymań w ARC. Według Apple Swift Language Guide, 2026, słabe referencje deklarowane są z kluczowym słowem weak i zawsze mają typ opcjonalny. Gdy obiekt jest zwalniany, wszystkie słabe referencje do niego są automatycznie ustawiane na nil, co zapobiega wiszącym wskaźnikom i czyni słabe referencje bezpiecznym mechanizmem do przerywania retain cycle.
Najważniejsze
weak przed var; typ zawsze opcjonalny (?)Weak Reference — to nieposiadająca referencja do obiektu w ARC (Automatic Reference Counting). W przeciwieństwie do strong-referencji, która zwiększa retain count obiektu i gwarantuje jego życie, weak-referencja pozwala obiektowi zostać zwolnionym, nawet jeśli wciąż istnieją do niego referencje. Po zwolnieniu weak-referencja jest automatycznie ustawiana na nil — nazywa się to zeroing weak.
Zeroing weak — kluczowa cecha Swift i Objective-C runtime. Gdy licznik referencji obiektu osiąga zero i obiekt jest dealokowany, runtime przegląda wszystkie weak-referencje do tego obiektu (przechowywane w specjalnej weak-tabeli) i ustawia je na nil. Gwarantuje to, że odwołania do zwolnionej pamięci (use-after-free) są niemożliwe przez weak-referencje — każde odczytanie zwraca nil.
Według Apple WWDC 2012 Session 406, zeroing weak referencje wyeliminowały całą klasę błędów crash związanych z wiszącymi wskaźnikami (dangling pointers), które były powszechne w ręcznym zarządzaniu pamięcią (MRR). W MRR słabe referencje istniały tylko jako __unsafe_unretained — nie były zerowane, a odwołanie do zwolnionego obiektu prowadziło do EXC_BAD_ACCESS.
Omówimy składnię deklaracji weak-referencji w obu językach ekosystemu Apple. Mimo wspólnego runtime'u, składnia się różni, ale semantyka jest identyczna.
W Swift weak-referencje deklarowane są z kluczowym słowem weak przed var. Typ musi być zawsze opcjonalny (Type?), ponieważ referencja może zostać wyzerowana w każdej chwili. Stałe (let) nie mogą być weak — tylko zmienne.
class ViewController: UIViewController {
// weak-właściwości: tylko var, tylko optional
weak var delegate: ViewControllerDelegate?
weak var parentView: UIView?
weak var completionHandler: ((Bool) -> Void)? // ⚠️ domknięcia weak nie przechowują
// ⬆️ Błąd: weak można zastosować tylko do class-typów, nie do closure
}
Ważne: weak ma zastosowanie tylko do instancji klas (class-types), AnyObject i protokołów dziedziczących po AnyObject. Struct, enum i closure nie mogą być weak — są to typy wartościowe i nie uczestniczą w ARC.
W Objective-C weak-właściwości deklaruje się przez atrybut __weak lub modyfikator weak we właściwości:
// Objective-C: weak property
@interface MyViewController : UIViewController
@property (weak, nonatomic) id<MyDelegate> delegate;
@end
// Lokalna weak-zmienna
__weak MyObject *weakRef = someStrongObject;
Objective-C runtime również zapewnia zeroing weak, ale dodatkowo blokuje użycie weak z C-strukturami i niektórymi obiektami Core Foundation. Dla nich stosuje się __unsafe_unretained — bez zeroing.
Słabe referencje — nie uniwersalne rozwiązanie, a narzędzie do konkretnych scenariuszy. Używanie weak wszędzie prowadzi do nadmiernej złożoności i pogarsza czytelność. Omówmy prawidłowe scenariusze zastosowania.
Delegat — główny scenariusz dla weak. Obiekt-właściciel (np. UITableView) trzyma strong-referencję na siebie, a delegat (UIViewController) nie powinien posiadać tabeli. Apple SDK gwarantuje, że wszystkie delegate i dataSource są weak. Dla własnych protokołów zawsze używaj weak var delegate.
Gdy obiekt potomny musi referencjonować rodzica (np. ChildViewController dla dostępu do koordynatora), używaj weak-referencji. Rodzic posiada dziecko (strong), dziecko obserwuje rodzica (weak) — retain cycle wykluczony.
Capture list [weak self] — standardowy sposób uniknięcia retain cycle w domknięciach przechowywanych jako właściwości klasy. Jeśli self może zostać zwolniony przed zakończeniem domknięcia — weak self jest obowiązkowy.
| Scenariusz | Weak | Strong |
|---|---|---|
| Delegate | ✅ Zawsze weak | ❌ Retain cycle |
| Parent → Child | ❌ Nie potrzebne (rodzic powinien posiadać) | ✅ Strong |
| Child → Parent | ✅ Weak | ❌ Retain cycle |
| Asynchroniczny callback | ✅ [weak self] | ❌ Ryzyko retain cycle |
| Silne powiązanie (owned) | ❌ unowned | ✅ Strong |
Ogólna zasada: jeśli obiekt A posiada B (A → B strong), to B → A powinien być weak lub unowned. Kierunek strong-referencji zawsze powinien być od właściciela do podwładnego.
Zarówno weak, jak i unowned nie zwiększają retain count, ale różnią się zachowaniem po dealokacji obiektu. Wybór między nimi to kwestia gwarancji czasu życia.
Weak: automatycznie zerowany (nil), typ zawsze optional, wymaga unwrap przed użyciem. Bezpieczny — odwołanie do nil nie powoduje crasha.
Unowned: nie jest zerowany, typ non-optional. Jeśli obiekt został zwolniony, unowned-referencja staje się wiszącym wskaźnikiem — odwołanie do niej powoduje runtime crash. Unowned zakłada, że obiekt żyje nie krócej niż referencjonująca strona.
Weak wybieraj, jeśli: obiekt może być dealokowany w każdej chwili (delegat po zamknięciu ekranu), nie kontrolujesz czasu życia obiektu lub masz wątpliwości co do gwarancji. Weak — uniwersalny bezpieczny wybór.
Unowned wybieraj, jeśli: obiekt gwarantowanie nie może zostać zwolniony wcześniej niż referencjonujący (np. Customer → CreditCard, gdzie karta nie istnieje bez klienta). Unowned daje non-optional API bez unwrap, co jest wygodniejsze w kodzie.
class Order {
let id: Int
var items: [Item] = []
init(id: Int) { self.id = id }
// Silne powiązanie: Order posiada Item
func addItem(name: String) {
let item = Item(name: name, order: self)
items.append(item)
}
}
class Item {
let name: String
unowned let order: Order // ✅ unowned — Item nie żyje bez Order
init(name: String, order: Order) {
self.name = name
self.order = order
}
}
// Przykład z weak: delegat bez gwarancji czasu życia
protocol NetworkServiceDelegate: AnyObject {
func didReceiveResponse(data: Data)
}
class NetworkService {
weak var delegate: NetworkServiceDelegate? // ✅ weak — delegat może odejść
}
W przykładzie Item używa unowned, ponieważ element zamówienia nie może istnieć bez samego zamówienia — gwarancja czasu życia żelazna. NetworkService używa weak, ponieważ delegat (np. ViewController) może zostać zamknięty i zwolniony w każdej chwili.
Słabe referencje — potężne narzędzie, ale mają ograniczenia, które ważne jest zrozumieć dla prawidłowego stosowania w iOS development.
Słabe referencje są wolniejsze niż strong: przy każdym dostępie runtime sprawdza, czy obiekt nie został zwolniony (lookup w weak-tabeli). W zdecydowanej większości scenariuszy różnica jest nieodczuwalna, ale w gorących pętlach z milionami odwołań weak może być wąskim gardłem. Dla scenariuszy wysokiego obciążenia używaj strong i reorganizuj architekturę.
Struct, enum, tuple — typy wartościowe, nieuczestniczące w ARC. Próba zadeklarowania weak struct prowadzi do błędu kompilacji. Do przechowywania słabej referencji na value-typ użyj opakowania w class-typ lub domknięcia.
Zeroing weak jest wątkowo bezpieczny: jeśli obiekt jest zwalniany na jednym wątku, weak-referencja jest zerowana na wszystkich wątkach atomowo. Jednak odstęp między odczytaniem weak-referencji a jej użyciem może prowadzić do stanu wyścigu — obiekt zostaje zwolniony między pobraniem weak-referencji a jej użyciem. Rozwiązanie: strong-przechwycenie słabej referencji do lokalnej zmiennej.
// Race condition z weak w wielowątkowości
func performAsync() {
weak var weakSelf = self
queue.async {
// ⚠️ weakSelf może być nil między sprawdzeniem a użyciem
if weakSelf != nil {
weakSelf!.doSomething() // CRASH jeśli stał się nil
}
}
}
// ✅ Poprawka: silne przechwycenie na czas użycia
func performAsyncSafe() {
queue.async { [weak self] in
guard let strongSelf = self else { return }
strongSelf.doSomething() // strongSelf — lokalna strong-referencja
}
}
W bezpiecznym wariancie weak self jest przechwytywane, następnie natychmiast rozwijane do lokalnej strong-zmiennej strongSelf. Jeśli self wciąż żyje — pozostanie żywy na czas wykonania bloku. Jeśli nie — guard zadziała i kod nie zostanie wykonany. Ten idiom — standardowy wzorzec dla asynchronicznych domknięć w Swift.
IBOutlet w Interface Builder powinny być weak, ponieważ hierarchia widoków już trzyma strong-referencję na subview. Dublowanie strong-referencji w kontrolerze nie tworzy retain cycle, ale jest zbędne. Słaba referencja na outlet — zalecenie Apple, choć wielu programistów używa strong dla uproszczenia kodu.
Często zadawane pytania
Nie, weak może wskazywać tylko na istniejący obiekt lub nil. Przy tworzeniu nowego obiektu najpierw otrzymujesz strong-referencję (przez inicjalizator), i dopiero potem możesz przypisać słabą referencję. weak nil na początku — normalny stan.
Weak opiera się na ARC, który zarządza tylko reference-typami (klasami). Value-typy (struct, enum) są kopiowane przy przypisaniu i nie mają retain count. Dla słabego powiązania value-typów używaj domknięć lub opakowań w class z weak-właściwością.
Każdy dostęp do weak-referencji wykonuje lookup w runtime-tabeli. W pętli z milionami iteracji może to być 2–5 razy wolniejsze niż strong-referencja. Dla gorących ścieżek skopiuj weak do lokalnej strong-zmiennej przed pętlą.
Gdy wszystkie strong-referencje do obiektu zostaną utracone — na końcu zakresu, przy ponownym ustawieniu właściwości, przy zamknięciu ekranu. W środowisku wielowątkowym może to nastąpić między dwoma liniami kodu. Zawsze sprawdzaj weak przez guard let lub if let.
Semantycznie identyczne: oba zapewniają zeroing weak. Różnice: Swift wymaga optional-typu i var, Objective-C używa modyfikatora property. Objective-C obsługuje również __unsafe_unretained — słabą referencję bez zeroing (ryzyko wiszącego wskaźnika).
Podsumowanie
weak var + optional-typ; tylko class-typy i protokoły AnyObjectOpracujemy 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ż