Как правильно обращаться с освобожденным делегатом от очереди
В моем текущем проекте несколько контроллеров представлений (например, vc
) порождают объекты NSOperation (например, operation
), которые выполняются на статическом NSOperationQueue. Пока операция ожидает или работает, она будет отчитываться перед контроллером представления через делегирование (operation.delegate = vc
, назначено не сохранено).
Эти операции могут занять некоторое время, и, в то же время, приложение может отключить контроллер вида (путем добавления их в стек навигационного контроллера).
Пока все намеренно. Класс, содержащий статический NSOperationQueue, имеет возможность вернуться к операциям, поэтому контроллеры представления не сохраняют их. Они просто выделяют /init/autoreleased и помещаются в очередь.
Теперь это также вызывает проблему. После того, как диспетчер представлений освободится, любые вызовы вызываемого делегата NSOperation вызовут плохое нарушение доступа. Насколько я понимаю, невозможно проверить, был ли объект в указателе освобожден, как указано в этом вопросе.
Одно исправление, о котором я могу думать, - это сохранение операции и установка операции .delegate на nil на dealloc. Но это было бы моим наименее популярным решением, так как это обеспечило бы много дополнительных иваров/свойств, чтобы отслеживать.
Поэтому мой вопрос заключается в том, есть ли другие способы обойти эту проблему, и если да, можете ли вы набросать ее здесь?
Cheers,
ЕР.
РЕШЕНИЕ. Самый подходящий для меня подход - небольшая вариация ответа Гильяно:
-
Реализация каждого протокола делегатов в диспетчере очередей невозможна (20+ разных протоколов с 50 + методами), поэтому я сохранил прямые назначения делегатов. То, что я изменил, было классом, вызывающим вызов назначения. Раньше это был класс (и делегат), который создал запрос, но теперь он выгружается в диспетчер очереди.
-
Менеджер очередей, рядом с назначением делегата для операции, также содержит вторичный изменяемый словарь для отслеживания пар делегатов/операций.
-
Каждый экземпляр делегата вызывает метод [QueueManager invalidateDelegate:self]
для освобождения, который затем ищет запрос, принадлежащий делегату, и nils его. Параметр/оператор-оператор словаря также удаляется, чтобы обеспечить правильное освобождение операции.
-
Наконец, с KVO, наблюдающим свойство isFinished
для каждой операции, изменяемый dict сохраняется в чистоте, чтобы убедиться, что все операции сохраняют отсчеты фактически освобождаются после их завершения.
Спасибо Guiliano за предоставленную подсказку для использования KVO для взлома этого!
Ответы
Ответ 1
Я бы предложил просмотреть вашу архитектуру и переместить делегат в класс (предположим QueueManager), который управляет очередью, вместо того, чтобы иметь делегат в каждой операции:
-
Создайте QueueManagerDelegate, чтобы
реализует метод (ы), который вам нужен
уведомлять viewControllers
-
В QueueManager добавьте наблюдателя KVO для свойства isFinished каждого NSOperation (сделайте это перед добавлением операции в очередь;))
-
В обратном вызове вызова KVO метод (делегаты) делегата вам нужен, только если делегат равен!= nil
-
Добавьте метод invalidate в QueueManager и вызовите этот метод в методе dealloc вашего UIViewController (s)
- (Недействительными) Invalidate { self- > delegate = nil; }
если вам нужно обновить KVO: руководство по программированию Kvo
Ответ 2
Лучший совет здесь - рассмотреть архитектуру приложения, чтобы избежать таких ситуаций. Однако, если текущий код не может быть изменен, почему-то вы можете использовать NSNotificationCenter. Каждый раз, когда ваш диспетчер просмотров освобожден, вы можете опубликовать уведомление, это уведомление должно быть уловлено владельцем NSOperationQueue, простым циклом foreach в обработчике уведомлений на nil делегат для освобожденный контроллер просмотра. Должен сделать трюк.
Ответ 3
Вы также должны проверить, чтобы любые делегаты, если не ноль, также могли отвечать на сообщение из завершения операции. Вы выполняете это с помощью функции respondsToSelector
, которой необходимы все подклассы NSObject.
В моих проектах я отвлек эту проверку на категорию в NSObject, которая позволяет мне безопасно вызывать делегаты с произвольным числом аргументов объекта:
- (void) dispatchSelector:(SEL)selector
target:(id)target
objects:(NSArray*)objects
onMainThread:(BOOL)onMainThread {
if(target && [target respondsToSelector:selector]) {
// Do your delegate calls here as you please
}
}
Здесь вы можете увидеть полный пример: https://github.com/chaione/ChaiOneUtils/blob/master/Categories/NSObject-Dispatch.m