Блокировка переключения в iOS

У меня проблемы с несколькими потоками. Вот ситуация:

Я делаю асинхронные запросы на бэкэнд и, в некоторых случаях, должен их отменять. Отмена запросов происходит в отдельном потоке. Все запросы на бэкэнд отменены, и, когда я выхожу из экрана, несколько экземпляров класса освобождаются.

Когда у меня есть запрос на отмену заказа, все работает нормально. Однако иногда метод cancel вызывается, когда я уже посередине метода финиша (который требует немного времени из-за декодирования и преобразования данных). В таких случаях приложение выходит из строя, отправляется сообщение на освобожденный экземпляр. Это не исключение, которое можно легко кэшировать, я получаю сбой даже тогда, когда проверяю наличие экземпляров строки раньше. Собственно, если я правильно понимаю, экземпляр класса, в котором находится финиш и метод отмены, освобождается. Nevermind, проблема в том, что нить переключается в середине метода финиша, и мне нужно это предотвратить.

Мой вопрос: есть ли способ блокировать переключение потока в середине метода? Объявить этот метод в целом (транзакцию)? Или есть еще одно стандартное решение для такой проблемы?

Я прочитал этот пост, но я действительно не понимаю, можно ли его использовать в моем случае или как это сделать.

EDIT: Отмена

for (XXX *request in [XXX sharedQueue].operations) { [request setDelegate:nil]; [request setDownloadProgressDelegate:nil]; [request setUploadProgressDelegate:nil]; [request setQueue:nil]; [request cancel]; } 

XXX – это класс, используемый для запросов.

Порядок методов

Вот порядок, в котором используются методы (в случае ошибки). Handler – это класс для обработки запросов.

 Handler 1: make request Handler 1: finish begin Handler 2: cancel begin Handler 2: cancel end Handler 2: dealloc Handler 1: dealloc Handler 1: finish middle 

Обработчики 1 и 2 являются двумя экземплярами класса. Первый из них освобожден в середине метода финиша, поэтому в конце этого я получаю сбой. Расселение это нормально, потому что после отмены я перехожу к другому виду и в основном все освобождается.

Мои идеи для решения – либо каким-то образом предотвратить возврат к методу финиша, либо выполнить весь метод завершения перед переключением потока. К сожалению, я понятия не имею, как один из них может быть реализован.

Следующий подход может помочь вам:

  1. добавьте флаг контроллеру, управляющему всеми запросами;

  2. когда вводится блок завершения запроса, выполните следующие действия:

     completionBlock:^() { @synchronized(self.myFinishFlag) { ... } } 
  3. в вашем методе отмены сделайте следующее:

     -(void)userCancelledRequests:(id)sender { @synchronized(self.myFinishFlag) { ... } } 

Это задержит выполнение «bodyCancelledRequests», body if a finish block is currently running (ie, locking Е. body if a finish block is currently running (ie, locking self.myFinishFlag`).

Надеюсь это поможет.

Похоже, что метод cancel класса XXX неправильно реализован.

Предположим, что существует асинхронная операция типа XXX которая отвечает на сообщение cancel . Чтобы функционировать надежно, должны выполняться следующие требования:

  1. Сообщение cancel может быть отправлено клиентом из любого потока.

  2. Сообщение cancel может быть отправлено в любое время и несколько раз.

  3. Когда операция получает сообщение cancel , она останавливает ее асинхронную задачу и правильно очищает ее в следующей «точке отмены». Примечание: это может происходить асинхронно по отношению к методу cancel . Реализация должна быть «потокобезопасной»!

  4. Получатель должен уведомить делегата о том, что он был отменен (например, в обработчике сбоев).

  5. Кроме того, не должно быть необходимости, чтобы вызывающий пользователь каким-либо образом сбросил делегат (-ы) или подготовил получателя до отправки сообщения об cancel .

Эти требования должны выполняться путем реализации класса XXX .

Теперь предположим, что у вас есть метод внутренней finish для этой операции и позволяет предположить, что это последний метод, который будет выполняться операцией. Когда этот метод выполняется, и когда операция получает сообщение cancel одновременно , реализация должна гарантировать, что cancel не действует, поскольку уже слишком поздно: последняя возможность отменить операцию была передана. Есть несколько способов, как это сделать.

Если это так, то следующий код должен должным образом отменить ваши операции:

 for (XXX *request in [XXX sharedQueue].operations) { [request cancel]; } 

Редактировать:

Пример реализации «NSOperation like» метода cancel и finish :

Заметка:

Доступ к иварам должен быть синхронизирован ! Доступ к Ivars будет осуществляться различными методами. Все обращения будут сериализованы через приватную очередь последовательной отправки с именем «sync_queue».

 - (void) cancel { dispatch_async(self.sync_queue, ^{ if (_isCancelled || _isFinished) { return; } [self.task cancel]; // will sets _isCancelled to YES [self finish]; // will set _isFinished to YES }); } - (void) finish { assert(<execution context equals sync_queue>); if (_isFinished) return; completionHandler_t onCompletion = self.completionHandler; if (onCompletion) { id result = self.result; dispatch_async(dispatch_get_global_queue(0, 0), ^{ onCompletion(result); }); }; self.completionHandler = nil; self.task = nil; self.isExecuting = NO; self.isFinished = YES; } 

Лучший способ блокировки – выполнить все операции в выделенной очереди. Когда вы хотите отменить, сделайте следующее:

 dispatch_async(_someQueue, ^{ for (XXX *request in [XXX sharedQueue].operations) { [request setDelegate:nil]; [request setDownloadProgressDelegate:nil]; [request setUploadProgressDelegate:nil]; [request setQueue:nil]; [request cancel]; } }); 

Затем, когда завершен обратный вызов, вы можете сделать это:

 dispatch_async(_someQueue, ^{ if ([request isCancelled] == NO) { // Process request } }); 

Пока _someQueue не помечен как параллельная очередь, это должно сделать трюк.