Ответ 1
Итак, когда обратный вызов, наконец, запущен, self.myLabel.text может привести к сбою, поскольку контроллер просмотра, к которому я обращался, уже может быть освобожден.
Если self
было импортировано в закрытие в качестве сильной ссылки, гарантируется, что self
не будет освобожден до завершения завершения закрытия. То есть контроллер просмотра все еще жив, когда замыкание вызвано - даже если его вид не отображается в это время. Заявление self.myLabel.text = "it succeeded"
будет выполнено, но даже метка не будет видна, это не сработает.
Есть, однако, тонкая проблема, которая может привести к краху при определенных обстоятельствах:
Предположим, что замыкание имеет последнюю и только сильную ссылку на контроллер вида. Закрытие заканчивается и впоследствии освобождается, что также освобождает последнюю сильную ссылку на контроллер вида. Это неизбежно вызовет метод dealloc
контроллера вида. Метод dealloc
будет выполняться в том же потоке, где будет выполняться закрытие. Теперь, когда диспетчер представлений является объектом UIKit
, он ДОЛЖЕН быть гарантирован, что все методы, отправленные этому объекту, будут выполняться в основном потоке. Таким образом, IFF dealloc
будет фактически выполнен на каком-то другом потоке, ваш код может упасть.
Для подходящего подхода потребуется "отменить" асинхронную задачу, результат которой больше не нужен контроллеру представления, когда он "закрыт". Это, конечно, требует отмены вашей "задачи".
Чтобы устранить некоторые проблемы с вашим предыдущим подходом, вы можете зафиксировать слабую ссылку на свой контроллер представления вместо сильной ссылки при определении закрытия. Это не помешало бы асинхронной задаче доработать ее до завершения, но в обработчике завершения вы можете проверить, жив ли контроллер просмотра, и просто выручить, если он больше не существует.
И если вам нужно "сохранить" объект UIKit в некотором закрытии, который может выполняться на некотором произвольном потоке, позаботьтесь о том, чтобы это была последняя сильная ссылка, и убедитесь, что эта последняя сильная ссылка освобождается в основном потоке.
Смотрите также: Использование слабой функции self в функции dispatch_async
Edit:
Мой вопрос будет: у закрытий есть что-то особенное, что делает их хорошим выбором в сценариях, подобных этому, вместо того, чтобы возвращаться к шаблону делегирования?
Я бы сказал, что закрытие - это "лучший" подход во многих случаях использования:
Делегаты более склонны к таким проблемам, как круговые ссылки, чем закрытие (поскольку они "принадлежат" объекту, и этот объект может быть захвачен как переменная в делегате).
Классический прецедент для закрытия в качестве обработчиков завершения также улучшает "локальность" вашего кода, делая его более понятным: вы указываете, что произойдет, когда задача завершится сразу после оператора, вызывающего задачу - независимо от того, как долго это может брать.
Огромное преимущество при закрытии по сравнению с обычными "функциями" заключается в том, что закрытие фиксирует весь "контекст" в момент его определения. То есть он может ссылаться на переменные и "импортировать" их в закрытие в то время, когда он определен, - и использовать его, когда он выполняется, независимо от того, когда это происходит, и когда исходный "стек" во время определения отсутствует уже.