Разрешено ли объявить weak-свойство типа замыкания в Swift, чтобы разорвать цикл с его владельцем?
Нет. В Swift weak применим только к экземплярам классов и class-bound протоколам, а тип замыкания не удовлетворяет этому ограничению. Чтобы разорвать цикл, обычно делают слабым захват владельца внутри замыкания, а не саму ссылку на замыкание.
ARC автоматизирует подсчёт сильных ссылок на экземпляры классов, но не определяет семантику владения для любых ссылочных по реализации значений. Поэтому Swift явно разделяет обычные ссылочные значения, которыми можно владеть, и специальные слабые ссылки на классы, которые runtime умеет обнулять при уничтожении объекта.
Слабая ссылка появилась как средство описать отношение «наблюдаемый объект не владеет владельцем». Для замыканий аналогичная проблема решается через capture list, потому что замыкание само может захватывать объекты.
Замыкание часто хранится в свойстве объекта, например как callback. Если это замыкание сильно захватывает тот же объект, возникает цикл: объект владеет замыканием, а замыкание владеет объектом.
Попытка объявить само свойство-замыкание слабым не является допустимым решением: компилятор отклонит такое объявление. Кроме того, даже гипотетически слабая ссылка на callback не объяснила бы, как именно замыкание должно владеть захваченным объектом.
Тип замыкания является ссылочным значением, но это не делает его допустимым типом для weak. Ограничение weak связано не с общей ссылочной природой значения, а с тем, что оно должно представлять экземпляр класса или class-bound протокола, для которого runtime поддерживает обнуление слабых ссылок.
Цикл разрывают ослаблением захвата объекта внутри замыкания:
Здесь Controller сильно владеет замыканием через callback, но замыкание хранит self слабо. После исчезновения остальных сильных ссылок Controller может быть уничтожен, а слабая ссылка внутри замыкания автоматически станет nil.
Если вместо [weak self] использовать обычный захват self, получится цикл. Если жизненный контракт гарантирует, что владелец переживёт callback, можно рассмотреть unowned, но ошибка в таком контракте приведёт к аварийному завершению при обращении к уже уничтоженному объекту.
Экран хранит callback для обработки события, а callback вызывает метод этого же экрана. Вариант с сильным захватом прост, но удерживает экран и его callback друг через друга; deinit экрана не вызывается после закрытия.
Вариант с попыткой сделать свойство callback слабым невозможен: тип замыкания не поддерживает weak. Вариант с [unowned self] устраняет цикл, но безопасен только при строгой гарантии, что callback не будет вызван после уничтожения экрана.
Выбранное решение — [weak self] и условный вызов метода. Оно не создаёт цикл и безопасно обрабатывает поздний вызов callback: после уничтожения экрана замыкание ничего не делает. Цена решения — необходимость учитывать, что self при выполнении callback может уже отсутствовать.
1. Почему замыкание считается ссылочным значением, но всё равно не поддерживает weak?
Ссылочная семантика и допустимость weak — разные свойства. Замыкание может храниться и копироваться как ссылочное значение, однако weak в Swift ограничен экземплярами классов и class-bound протоколами. Для захвата объектов замыкание использует собственные правила capture list.
2. Что именно нужно сделать слабым при цикле между объектом и callback?
Слабым обычно делают захват владельца внутри callback: [weak self]. Сам callback остаётся сильным свойством объекта, поскольку объекту нужно хранить и вызывать его. Цикл исчезает, потому что обратная связь от замыкания к владельцу больше не увеличивает счётчик сильных ссылок.
3. Может ли callback с [weak self] всё равно временно удерживать объект?
Да. При чтении слабой ссылки и последующем создании сильной локальной ссылки объект может оставаться живым до конца текущего использования. Это нормальная временная гарантия выполнения операции, но она не превращает слабый захват в постоянное владение и не создаёт цикл сама по себе.