Объединение асинхронных вызовов в синхронный поток блокировки?

Я пишу модуль iOS, который в настоящее время отправляет электронное письмо асинхронно (используя делегатов). Он использует SKPSMTPMessage, который отлично работает. Моя проблема заключается в том, что клиент хочет, чтобы код полностью блокировал поток до тех пор, пока сообщение не было отправлено (или не было отправлено). Поэтому они в основном запрашивают синхронное решение, когда в настоящее время он попытается отправить электронное письмо, а затем вернуться из этого блока кода до отправки сообщения электронной почты.

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

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

Любая информация или даже другие идеи оценены.

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

EDIT: Спасибо за все входные данные. Как было предложено одним из ответов, я закончил тем, что просто использовал цикл while, который ждал возвращения делегатов, но пусть runLoop будет продолжать так же:

while( ![messageDelegate hasFinishedOrFailed] ){
    // Allow the run loop to do some processing of the stream
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
}

Ответ 1

Я не верю, что есть способ сделать это без изменения SKPSMTPMessage. Класс фактически не использует отдельные потоки; вместо этого он использует NSStream совместно с циклом запуска потока, чтобы избежать блокировки:

[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
                           forMode:NSRunLoopCommonModes];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
                            forMode:NSRunLoopCommonModes];

Поток действует как источник входного сигнала для цикла ; цикл выполнения затем может обрабатывать другие события до тех пор, пока что-то не произойдет с потоком, и в этот момент поток уведомляет его делегата. Все все еще происходит на исходной (основной) теме; если вы попытаетесь заблокировать его самостоятельно, вы также заблокируете цикл выполнения, и он не сможет ничего сделать с потоком.

Другие уже указали, что блокирование основного потока - плохая идея; кроме проблем с UX, система может прекратить любое приложение, которое не реагирует на события в течение слишком длительного периода. Тем не менее, вы можете поместить всю настройку сообщения в фоновый режим, предоставив ей собственный цикл запуска потока для работы и заблокировать основной поток, используя GCD. К сожалению, я не могу придумать, как делегат может сигнализировать, что это сделано без опроса.

dispatch_queue_t messageQueue;
messageQueue = dispatch_queue_create("com.valheru.messageQueue", NULL);

// dispatch_sync blocks the thread on which it called
dispatch_sync(messageQueue, ^{
    [messageManager tryToDeliverMessage];
});
dispatch_release(messageQueue);

Где tryToDeliverMessage выглядит примерно так:

- (void) tryToDeliverMessage {
    // Create the message and let it run...

    // Poll a flag on the delegate
    while( ![messageDelegate hasFinishedOrFailed] ){
        // Allow the run loop to do some processing of the stream
        [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
    }

    return;
}

Ответ 2

Я бы попробовал использовать семафоры отправки. На странице man для dispatch_semaphore_create(3):

dispatch_semaphore_t sema = dispatch_semaphore_create(0);

dispatch_async(queue, ^{
    foo();
    dispatch_semaphore_signal(sema);
});

bar();

dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_release(sema);
sema = NULL;

Вызов dispatch_semaphore_wait() будет заблокирован до завершения вызова dispatch_semaphore_signal().

Ответ 3

Исправьте меня, если я неправильно понимаю ограничения, но конечная цель - не допустить перехода пользователя после отправки электронной почты? Если это так, то, возможно, добавление какого-то полного обзора прогресса на экране, поскольку подобие окна будет хорошим решением. Отключите взаимодействие с пользователем и удалите представление, когда вы получите успешный или неудачный обратный вызов через методы делегата.

Ответ 4

while (! [messageDelegate hasFinishedOrFailed]) {   // Разрешить циклу выполнения выполнять некоторую обработку потока   [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 1]]; }

это приведет к тому, что представление таблицы может не прокручиваться иногда, а представление предупреждений не может отклонить