Weak Reference — co to jest, składnia i zastosowanie w programowaniu mobilnym

Autor: IT Sectr Opublikowano: 2026-03-30 Czas czytania: 9 min

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 Reference — referencja nie wpływająca na retain count obiektu; po zwolnieniu obiektu jest zerowana
  • Deklaracja — kluczowe słowo weak przed var; typ zawsze opcjonalny (?)
  • Zastosowanie — delegaty, domknięcia, relacje parent-child do przerywania retain cycle
  • Bezpieczeństwo — automatyczne ustawienie na nil po dealokacji obiektu (zeroing weak)
  • Różnica od unowned — weak jest zerowany i bezpieczny, unowned nie jest zerowany i wymaga gwarancji czasu życia

Czym jest Weak Reference?

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.

Składnia weak w Swift i Objective-C

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.

Swift

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.

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

Objective-C

W Objective-C weak-właściwości deklaruje się przez atrybut __weak lub modyfikator weak we właściwości:

objective-c
// 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.

Kiedy stosować słabe referencje

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.

Delegaty (Delegate pattern)

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.

Parent-Child ze sprzężeniem zwrotnym

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.

Asynchroniczne domknięcia

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.

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

Weak vs Unowned: porównanie i scenariusze

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.

Różnice

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.

Kiedy wybrać weak

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.

Kiedy wybrać unowned

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.

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

Ograniczenia słabych referencji i pułapki

Słabe referencje — potężne narzędzie, ale mają ograniczenia, które ważne jest zrozumieć dla prawidłowego stosowania w iOS development.

Wydajność weak

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

Weak nie ma zastosowania do value-typów

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.

Weak w wielowątkowości

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.

swift
// 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.

UIView i weak outlet

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

Czy weak-referencja może wskazywać na obiekt, który jeszcze nie został utworzony?

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.

Dlaczego weak działa tylko z class-typami?

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

Jak weak wpływa na wydajność w pętli?

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

Kiedy weak-referencja może stać się nil niespodziewanie?

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.

Czym weak różni się od __weak w Objective-C?

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 Reference — nieposiadająca referencja, nie zwiększająca retain count i automatycznie zerowana przy dealokacji
  • Składniaweak var + optional-typ; tylko class-typy i protokoły AnyObject
  • Zeroing weak — runtime zeruje wszystkie weak-referencje do zwolnionego obiektu, zapobiegając wiszącym wskaźnikom
  • Scenariusze — delegaty, parent-child ze sprzężeniem zwrotnym, asynchroniczne domknięcia ([weak self])
  • Weak vs Unowned — weak jest zerowany (bezpiecznie), unowned nie jest zerowany (ryzyko crasha, ale non-optional)
  • Wydajność — weak wolniejszy niż strong z powodu lookup w runtime-tabeli; dla gorących ścieżek kopiuj do strong
  • Zalecenie — jeśli nie jesteś pewien gwarancji czasu życia — wybieraj weak

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ż