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.