Почему добавление списка захвата закрытия препятствует освобождению моего экземпляра?

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 и посмотрим, что произойдет в обоих случаях.

Другие вопросы по тегам