Sobes.tech
Middle

Czy zawsze trzeba używać weak lub unowned w liście przechwytywania?

sobes.tech AI

Odpowiedź od AI

Nie, nie zawsze.

Używamy weak lub unowned w liście przechwytywania, aby zapobiec cyklom zatrzymania.

Cykl zatrzymania występuje, gdy dwa lub więcej obiektów odwołuje się do siebie nawzajem w sposób silny, uniemożliwiając zwolnienie pamięci.

Należy używać weak lub unowned, gdy zamknięcie silnie odwołuje się do self (lub innej zmiennej instancji klasy/aktor), a z kolei self (lub inny obiekt) ma silne odniesienie do tego zamknięcia.

Przykłady sytuacji, w których jest to konieczne:

  • Operacje asynchroniczne (żądania sieciowe, przetwarzanie danych)
  • Wzorzec Obserwatora
  • Bloki animacji
  • Kolejki dispatch

Jeśli zamknięcie nie przechwytuje self (lub innej zmiennej klasy/aktora, która ma silne odniesienie do zamknięcia), lub przechwytuje typy strukturalne/enumeracje, użycie weak lub unowned nie jest konieczne.

Różnice między weak a unowned:

Typ Opis Kiedy używać
weak Słabe (opcjonalne) odniesienie. Może stać się nil. Gdy cykl życia zamknięcia jest krótszy lub równy cyklowi życia obiektu przechwytywanego, i obiekt może stać się nil przed zakończeniem zamknięcia.
unowned Nieoptymalne (opcjonalne) odniesienie. Nie może stać się nil. Gdy cykl życia zamknięcia jest krótszy lub równy cyklowi życia obiektu przechwytywanego, i jest gwarantowane, że obiekt będzie istniał do końca zamknięcia. Jeśli obiekt zostanie zwolniony przed wykonaniem zamknięcia, wystąpi błąd (odniesienie unowned do zlikwidowanej instancji).

Przykład użycia weak w liście przechwytywania:

class MyClass {
    var name: String = "Test"

    func performAsyncOperation() {
        // Zamknięcie przechwytuje `self`.
        // MyClass może zostać zwolniony przed zakończeniem operacji asynchronicznej.
        DispatchQueue.global().async { [weak self] in
            guard let self = self else {
                // `self` został zwolniony, wychodzimy
                return
            }
            print("Operacja zakończona dla \(self.name)")
        }
    }

    deinit {
        print("MyClass zwolniony")
    }
}

Przykład użycia unowned w liście przechwytywania:

class ViewController {
    var dismissButton: UIButton!

    func setupButton() {
        dismissButton.addTarget(self, action: #selector(dismiss), for: .touchUpInside)

        // Zamknięcie przechwytuje `self`.
        // Zakładamy, że `self` (ViewController) żyje dłużej lub tak długo jak akcja przycisku.
        // W tym przypadku, target-action nie jest zamknięciem, ale ilustruje koncepcję `unowned` w silnych cyklach odniesień.
        // Gdyby to *była* zamknięcie, np. w obsłudze UIControl.Event,
        // i ViewController istnieje podczas kliknięcia:
        // dismissButton.addAction(UIAction { [unowned self] _ in
        //     self.dismiss(animated: true, completion: nil)
        // }, for: .touchUpInside)
    }

    @objc func dismiss() {
        // Zamknij kontroler widoku
    }
}

Jeśli zamknięcie nie tworzy cyklu zatrzymania, użycie weak lub unowned nie jest konieczne i może nawet prowadzić do niepotrzebnych sprawdzeń (weak) lub potencjalnych awarii (unowned).

Na przykład, zamknięcie, które nie przechwytuje self lub innego obiektu, który ma silne odniesienie do zamknięcia:

func fetchData(completion: @escaping (Data) -> Void) {
    URLSession.shared.dataTask(with: URL(string: "...")!) { data, response, error in
        guard let data = data else { return }
        // Tu nie ma przechwytywania `self` lub innych obiektów, które mogą wywołać cykl zatrzymania.
        completion(data)
    }.resume()
}

W tym przypadku [weak self] lub [unowned self] w liście przechwytywania nie są potrzebne.