Что такое циклы удержания (retain cycles) в iOS?
AI答案
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 внутри замыкания, предотвращая цикл удержания.