Что такое циклы удержания (retain cycles) в iOS?
Sobes Copilot
Получайте ответы в реальном времени
Ответ от нейросети
sobes.tech AI
Циклы удержания возникают, когда два или более объекта имеют сильные ссылки друг на друга, создавая "замкнутый круг". Вследствие этого, счетчик ссылок каждого объекта никогда не достигает нуля, и он не может быть освобожден ARC (Automatic Reference Counting). Это приводит к утечкам памяти, так как объекты остаются в памяти даже после того, как они перестают использоваться.
Пример:
class Person {
let name: String
var apartment: Apartment?
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
class Apartment {
let unit: String
var tenant: Person?
init(unit: String) {
self.unit = unit
print("Apartment \(unit) is being initialized")
}
deinit {
print("Apartment \(unit) is being deinitialized")
}
}
В этом примере, если 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) is being initialized")
}
deinit {
print("Apartment \(unit) is being deinitialized")
}
}
Теперь, когда Person объект освобождается, ссылка tenant в Apartment автоматически становится nil, разрывая цикл удержания и позволяя Apartment также быть освобожденным.
Таблица сравнения weak и unowned:
| Характеристика | weak |
unowned |
|---|---|---|
| Тип ссылки | Опциональный | Неопциональный |
| Может быть nil? | Да, при освобождении объекта | Нет |
| Когда использовать | Когда объект может быть nil |
Когда объект не может быть nil |
| Производительность | Немного медленнее из-за проверки на nil |
Слегка быстрее |
Также циклы удержания могут возникать в замыканиях.
Когда замыкание захватывает instance properties или 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("Value is now \(self.value)")
}
}
}
В данном примере [weak self] создает слабую ссылку на self внутри замыкания, предотвращая цикл удержания.