Почему добавление списка захвата закрытия препятствует освобождению моего экземпляра?
class Name {
var name: String
init(name: String) {
self.name = name
}
deinit {
print("\(name) deinit")
}
}
var x: Name? = Name(name: "abc")
var someClosure = {
print("\(x?.name)")
}
someClosure()
x = nil
И тогда консоль выведет:
Optional("abc")
abc deinit
Видно, что была вызвана функция deinit. Так что это не образует сильный референсный цикл. Но если я добавлю список захвата к закрытию:
var someClosure = { [x] in
print("\(x?.name)")
}
Консоль выведет:
Optional("abc")
И функция "deinit" не была вызвана. Таким образом, объект и ссылка образуют сильный ссылочный цикл.
Какова причина? В чем разница между этими двумя условиями?
2 ответа
Во-первых, в обоих случаях нет строгого цикла сохранения - у вас просто есть глобальная переменная замыкания, которая содержит сильную ссылку на экземпляр вашего класса, поэтому предотвращает его освобождение.
В вашем первом примере, когда вы захватываете x
в закрытии:
var someClosure = {
print("\(x?.name)")
}
То, что вы получили (по сути), является ссылкой на ссылку, то есть закрытие имеет ссылку на хранилище x
, который затем имеет ссылку на ваш экземпляр класса. Когда вы установите x
в nil
- закрытие все еще имеет ссылку на хранение x
, но сейчас x
не имеет ссылки на ваш экземпляр класса. Таким образом, ваш экземпляр класса больше не имеет каких-либо сильных ссылок на него и может быть освобожден.
Во втором примере, когда вы используете список захвата:
var someClosure = { [x] in
print("\(x?.name)")
}
Вы копируете x
сам - то есть вы копируете ссылку на экземпляр вашего класса. Поэтому закрытие сохранит ваш класс в течение всего времени его существования. настройка x
в nil
не влияет на ссылку замыкания на ваш экземпляр, так как у него есть своя сильная ссылка на него.
Это то, что я думаю, здесь происходит.
ARC добавляет счетчик ссылок к x
когда x
создано. Когда замыкание вызывается с помощью списка захвата, оно добавляет еще один счетчик (замыкание захватывает объект: таким образом, указывая компилятору, что ему нужно x
в будущем путем увеличения количества ссылок). Так x
теперь имеет счетчик ARC 2.
Когда вы назначаете x
в nil
это уменьшает счетчик ссылок на 1. Если счетчик равен 0, или есть только слабые ссылки на объект, то объект деинициализируется. Если число больше 0, а ссылки сильные, объект сохраняется.
Без явной передачи списка захвата замыканию он лишь слабо фиксирует объект. Затем ARC решает, что можно безопасно деиницировать объект, как только с ним выполнено закрытие. Передача списка захвата указывает ARC, что объект может понадобиться для того, чтобы замыкание могло работать несколько раз, поэтому делается строгая ссылка.
Сделай еще один звонок someClosure()
после x = nil
и посмотрим, что произойдет в обоих случаях.