Какви са циклите на задържане (retain cycles) в iOS?
sobes.tech AI
Отговор от AI
Циклите на задържане възникват, когато две или повече обекта имат силни препратки един към друг, създавайки "затворен кръг". В резултат, броячът на препратките на всеки обект никога не достига нула и той не може да бъде освободен от ARC (Automatic Reference Counting). Това води до течове на памет, тъй като обектите остават в паметта дори след като вече не се използват.
Пример:
class Person {
let name: String
var apartment: Apartment?
init(name: String) {
self.name = name
print("\(name) се инициализира")
}
deinit {
print("\(name) се освобождава")
}
}
class Apartment {
let unit: String
var tenant: Person?
init(unit: String) {
self.unit = unit
print("Apartment \(unit) се инициализира")
}
deinit {
print("Apartment \(unit) се освобождава")
}
}
В този пример, ако Person има силна препратка към Apartment, а Apartment има силна препратка към Person, при опит за освобождаване тези обекти няма да бъдат изтрити, тъй като броячите им на препратки не достигат нула.
Как да решите проблема с цикли на задържане:
Използвайте слаби (weak) или безвластни (unowned) препратки.
weak: Използва се, когато е възможно свързаният обект да станеnilпо време на жизнения цикъл на другия обект. Препратката става автоматичноnil, когато обектът, към който сочи, бъде освободен. Това е опционално.unowned: Използва се, когато е известно, че свързаният обект има същия или по-дълъг жизнен цикъл от обекта, който има препратка към него. Не може да бъдеnil.
Коригиран пример с използване на weak:
Променяме препратката tenant в класа Apartment на слаба:
class Apartment {
let unit: String
weak var tenant: Person? // използваме weak
init(unit: String) {
self.unit = unit
print("Apartment \(unit) се инициализира")
}
deinit {
print("Apartment \(unit) се освобождава")
}
}
Сега, когато обектът Person бъде освободен, препратката tenant в Apartment автоматично става nil, прекъсвайки цикъла на задържане и позволявайки Apartment също да бъде освободен.
Сравнение на weak и unowned:
| Характеристика | weak |
unowned |
|---|---|---|
| Тип препратка | Опционална | Неопционална |
Може ли да бъде nil? |
Да, когато обектът бъде освободен | Не |
| Кога да се използва? | Когато обектът може да стане nil |
Когато обектът не може да бъде nil |
| Производителност | Малко по-бавна поради проверка за nil |
Малко по-бърза |
Освен това, цикли на задържане могат да възникнат и в затваряния (closures).
Когато затваряне захваща свойства на инстанцията или self, то може да създаде силна препратка към обекта. Ако този обект също има силна препратка към затварянето (например чрез свойство-затваряне или делегат), възниква цикъл на задържане.
Как да решите проблема с цикли на задържане в затваряния:
Използвайте списък за захващане (capture list).
class SomeClass {
var value = 0
func doSomethingDelayed() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in // списък за захващане с weak self
guard let self = self else { return } // проверка за `nil` за weak self
self.value += 1
print("Стойността сега е \(self.value)")
}
}
}
В този пример, [weak self] създава слаба препратка към self вътре в затварянето, предотвратявайки цикъл на задържане.