Czym są cykle zatrzymania (retain cycles) w iOS?
sobes.tech AI
Odpowiedź od AI
Cykl zatrzymania występuje, gdy dwa lub więcej obiektów ma silne odniesienia do siebie, tworząc "zamknięty krąg". W wyniku tego licznik odniesień każdego obiektu nigdy nie osiąga zera i nie może zostać zwolniony przez ARC (Automatyczne Liczenie Odwołań). To prowadzi do wycieków pamięci, ponieważ obiekty pozostają w pamięci nawet po tym, jak przestają być używane.
Przykład:
class Person {
let name: String
var apartment: Apartment?
init(name: String) {
self.name = name
print("\(name) jest inicjalizowany")
}
deinit {
print("\(name) jest deinitializowany")
}
}
class Apartment {
let unit: String
var tenant: Person?
init(unit: String) {
self.unit = unit
print("Mieszkanie \(unit) jest inicjalizowane")
}
deinit {
print("Mieszkanie \(unit) jest deinitializowane")
}
}
W tym przykładzie, jeśli Person ma silne odniesienie do Apartment, a Apartment ma silne odniesienie do Person, to podczas próby zwolnienia tych obiektów nie zostaną one usunięte, ponieważ ich liczniki odniesień nie osiągną zera.
Jak rozwiązać problem cykli zatrzymania:
Użyj odniesień weak lub unowned.
weak: Używa się, gdy możliwe jest, że powiązany obiekt stanie sięnilw trakcie cyklu życia innego obiektu. Odniesienie staje się automatycznienil, gdy obiekt, na który się odwołuje, zostanie zwolniony. Jest opcjonalne.unowned: Używa się, gdy wiadomo, że powiązany obiekt ma ten sam lub dłuższy cykl życia niż obiekt, który go posiada. Nie może byćnil.
Przykład poprawiony z użyciem weak:
Zmienimy odniesienie tenant w klasie Apartment na słabe:
class Apartment {
let unit: String
weak var tenant: Person? // Używamy weak
init(unit: String) {
self.unit = unit
print("Mieszkanie \(unit) jest inicjalizowane")
}
deinit {
print("Mieszkanie \(unit) jest deinitializowane")
}
}
Teraz, gdy obiekt Person zostanie zwolniony, odniesienie tenant w Apartment automatycznie stanie się nil, przerywając cykl zatrzymania i pozwalając na zwolnienie Apartment.
Tabela porównawcza weak i unowned:
| Cecha | weak |
unowned |
|---|---|---|
| Typ odniesienia | Opcjonalny | Nie opcjonalny |
| Może być nil? | Tak, gdy obiekt jest zwalniany | Nie |
| Kiedy używać | Gdy obiekt może stać się nil |
Gdy wiadomo, że obiekt nie będzie nil |
| Wydajność | Nieco wolniejszy z powodu sprawdzania nil |
Nieco szybszy |
Ponadto, cykle zatrzymania mogą wystąpić również w zamknięciach (closures).
Gdy zamknięcie przechwytuje właściwości instancji lub self, może utworzyć silne odniesienie do obiektu. Jeśli ten obiekt ma również silne odniesienie do zamknięcia (np. przez właściwość zamknięcia lub delegata), powstaje cykl.
Jak rozwiązać problem cykli zatrzymania w zamknięciach:
Użyj listy przechwytywania (capture list).
class SomeClass {
var value = 0
func doSomethingDelayed() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in // Lista przechwytywania z weak self
guard let self = self else { return } // Sprawdzenie na nil dla weak self
self.value += 1
print("Wartość jest teraz \(self.value)")
}
}
}
W tym przykładzie, [weak self] tworzy słabe odniesienie do self wewnątrz zamknięcia, zapobiegając cyklowi zatrzymania.