Я бы сказал, предоставляет ли API один обработчик завершения или пару блоков успеха / неудачи, это в первую очередь вопрос личных предпочтений.
Оба подхода имеют свои плюсы и минусы, хотя существуют лишь незначительные различия.
Учтите, что есть и другие варианты, например, когда один обработчик завершения может иметь только один параметр, объединяющий конечный результат или потенциальную ошибку:
typedef void (^completion_t)(id result);
- (void) taskWithCompletion:(completion_t)completionHandler;
[self taskWithCompletion:^(id result){
if ([result isKindOfError:[NSError class]) {
NSLog(@"Error: %@", result);
}
else {
...
}
}];
Целью этой подписи является то , что обработчик завершения может быть использован в общем и в других API.
Например, в Category для NSArray есть метод, forEachApplyTask:completion:
который последовательно вызывает задачу для каждого объекта и прерывает цикл, если произошла ошибка. Поскольку этот метод сам по себе также асинхронный, он также имеет обработчик завершения:
typedef void (^completion_t)(id result);
typedef void (^task_t)(id input, completion_t);
- (void) forEachApplyTask:(task_t)task completion:(completion_t);
На самом деле, completion_t
как определено выше, достаточно универсально и достаточно для обработки всех сценариев.
Однако есть и другие способы для асинхронной задачи сигнализировать о своем уведомлении о завершении на сайт вызова:
обещания
Обещания, также называемые «Фьючерсы», «Отложенные» или «Отложенные», представляют собой конечный результат асинхронной задачи (см. Также: Вики- проекты и обещания ).
Первоначально обещание находится в состоянии ожидания. То есть его «ценность» еще не оценена и еще не доступна.
В Objective-C Promise будет обычным объектом, который будет возвращен из асинхронного метода, как показано ниже:
- (Promise*) doSomethingAsync;
! Начальное состояние Обещания «ожидает».
Тем временем асинхронные задачи начинают оценивать свой результат.
Также обратите внимание, что нет обработчика завершения. Вместо этого Обещание предоставит более мощные средства, с помощью которых сайт вызова может получить конечный результат асинхронной задачи, который мы скоро увидим.
Асинхронная задача, которая создала объект обещания, ДОЛЖНА в конечном итоге «разрешить» его обещание. Это означает, что, поскольку задача может быть выполнена успешно или не выполнена, она ДОЛЖНА либо «выполнить» обещание, передав ему оцененный результат, либо ДОЛЖНА «отклонить» обещание, передав ему ошибку, указывающую причину ошибки.
! Задача должна в конечном итоге решить свое обещание.
Когда Обещание разрешено, оно больше не может изменять свое состояние, включая его значение.
! Обещание может быть выполнено только один раз .
После того, как обещание было выполнено, сайт вызова может получить результат (независимо от того, провалился он или успешно). Как это будет сделано, зависит от того, реализовано ли обещание с использованием синхронного или асинхронного стиля.
Обещание может быть реализовано в синхронном или асинхронном стиле, что приводит к блокированию или неблокирующей семантике.
В синхронном стиле для получения значения обещания сайт вызова будет использовать метод, который будет блокировать текущий поток до тех пор, пока обещание не будет разрешено асинхронной задачей и не будет получен конечный результат.
В асинхронном стиле сайт вызова будет регистрировать обратные вызовы или блоки обработчиков, которые вызываются сразу после разрешения обещания.
Оказалось, что синхронный стиль имеет ряд существенных недостатков, которые эффективно побеждают достоинства асинхронных задач. Интересная статья о в настоящее время некорректной реализации «фьючерсов» в стандартной C ++ 11 lib может быть прочитана здесь: Broken promises - C ++ 0x futures .
Как, в Objective-C, сайт вызова может получить результат?
Ну, наверное, лучше показать несколько примеров. Есть несколько библиотек, которые реализуют Обещание (см. Ссылки ниже).
Однако для следующих фрагментов кода я буду использовать конкретную реализацию библиотеки Promise, доступную на GitHub RXPromise . Я автор RXPromise.
Другие реализации могут иметь похожий API, но могут быть небольшие и, возможно, тонкие различия в синтаксисе. RXPromise - это версия спецификации Promise / A + для Objective-C, которая определяет открытый стандарт для надежных и совместимых реализаций обещаний в JavaScript.
Все библиотеки обещаний, перечисленные ниже, реализуют асинхронный стиль.
Между различными реализациями есть довольно существенные различия. RXPromise внутренне использует диспетчеризацию lib, является полностью поточно-ориентированным, чрезвычайно легким, а также предоставляет ряд дополнительных полезных функций, таких как отмена.
Сайт вызова получает конечный результат асинхронной задачи через «регистрацию» обработчиков. «Обещание / спецификация A +» определяет метод then
.
Метод then
С RXPromise это выглядит следующим образом:
promise.then(successHandler, errorHandler);
где successHandler - это блок, который вызывается, когда обещание было «выполнено», а errorHandler - это блок, который вызывается, когда обещание было «отклонено».
! then
используется для получения конечного результата и определения успеха или обработчика ошибок.
В RXPromise блоки обработчиков имеют следующую подпись:
typedef id (^success_handler_t)(id result);
typedef id (^error_handler_t)(NSError* error);
Success_handler имеет параметр результат , который, очевидно , конечный результат асинхронной задачи. Аналогично, обработчик error_handler имеет параметр error, который является ошибкой, сообщаемой асинхронной задачей в случае ее сбоя.
Оба блока имеют возвращаемое значение. О чем это возвращаемое значение, скоро станет ясно.
В RXPromise then
это свойство, которое возвращает блок. Этот блок имеет два параметра: блок обработчика успеха и блок обработчика ошибок. Обработчики должны быть определены call-сайтом.
! Обработчики должны быть определены call-сайтом.
Таким образом, выражение promise.then(success_handler, error_handler);
является краткой формой
then_block_t block promise.then;
block(success_handler, error_handler);
Мы можем написать еще более краткий код:
doSomethingAsync
.then(^id(id result){
…
return @“OK”;
}, nil);
Код гласит: «Выполните doSomethingAsync, если это успешно, затем выполните обработчик успеха».
Здесь обработчик ошибок nil
означает, что в случае ошибки он не будет обрабатываться в этом обещании.
Другим важным фактом является то, что вызов блока, возвращенного из свойства then
, вернет Promise:
! then(...)
возвращает обещание
При вызове блока, возвращенного из свойства then
, «получатель» возвращает новое обещание, дочернее обещание. Получатель становится родительским обещанием.
RXPromise* rootPromise = asyncA();
RXPromise* childPromise = rootPromise.then(successHandler, nil);
assert(childPromise.parent == rootPromise);
Что это значит?
Что ж, благодаря этому мы можем «связывать» асинхронные задачи, которые эффективно выполняются последовательно.
Кроме того, возвращаемое значение любого из обработчиков станет «значением» возвращаемого обещания. Таким образом, если задача выполнена успешно с возможным результатом @ «OK», возвращенное обещание будет «разрешено» (то есть «выполнено») со значением @ «OK»:
RXPromise* returnedPromise = asyncA().then(^id(id result){
return @"OK";
}, nil);
...
assert([[returnedPromise get] isEqualToString:@"OK"]);
Аналогичным образом, при сбое асинхронной задачи возвращаемое обещание будет разрешено (то есть «отклонено») с ошибкой.
RXPromise* returnedPromise = asyncA().then(nil, ^id(NSError* error){
return error;
});
...
assert([[returnedPromise get] isKindOfClass:[NSError class]]);
Обработчик также может вернуть другое обещание. Например, когда этот обработчик выполняет другую асинхронную задачу. С помощью этого механизма мы можем «связать» асинхронные задачи:
RXPromise* returnedPromise = asyncA().then(^id(id result){
return asyncB(result);
}, nil);
! Возвращаемое значение блока-обработчика становится значением дочернего обещания.
Если дочернее обещание отсутствует, возвращаемое значение не имеет никакого эффекта.
Более сложный пример:
Здесь мы выполняем asyncTaskA
, asyncTaskB
, asyncTaskC
и asyncTaskD
последовательно - и каждая последующая задача принимает результат предыдущей задачи в качестве входных данных:
asyncTaskA()
.then(^id(id result){
return asyncTaskB(result);
}, nil)
.then(^id(id result){
return asyncTaskC(result);
}, nil)
.then(^id(id result){
return asyncTaskD(result);
}, nil)
.then(^id(id result){
// handle result
return nil;
}, nil);
Такая «цепь» также называется «продолжением».
Обработка ошибок
Обещания облегчают обработку ошибок. Ошибки будут «пересылаться» от родителя к потомку, если в обещании родителя не определен обработчик ошибок. Ошибка будет передана вверх по цепочке, пока ребенок не обработает ее. Таким образом, имея вышеуказанную цепочку, мы можем реализовать обработку ошибок, просто добавив еще одно «продолжение», которое имеет дело с потенциальной ошибкой, которая может произойти где-то выше :
asyncTaskA()
.then(^id(id result){
return asyncTaskB(result);
}, nil)
.then(^id(id result){
return asyncTaskC(result);
}, nil)
.then(^id(id result){
return asyncTaskD(result);
}, nil)
.then(^id(id result){
// handle result
return nil;
}, nil);
.then(nil, ^id(NSError*error) {
NSLog(@“”Error: %@“, error);
return nil;
});
Это похоже на более знакомый синхронный стиль с обработкой исключений:
try {
id a = A();
id b = B(a);
id c = C(b);
id d = D(c);
// handle d
}
catch (NSError* error) {
NSLog(@“”Error: %@“, error);
}
Обещания в целом имеют и другие полезные функции:
Например, имея ссылку на обещание, then
можно «зарегистрировать» столько обработчиков, сколько необходимо. В RXPromise регистрация обработчиков может происходить в любое время и из любого потока, так как он полностью потокобезопасен.
RXPromise имеет еще несколько полезных функциональных возможностей, которые не требуются в спецификации Promise / A +. Одним из них является «отмена».
Оказалось, что «отмена» является бесценной и важной особенностью. Например, сайт вызова, содержащий ссылку на обещание, может отправить ему cancel
сообщение, чтобы указать, что он больше не заинтересован в конечном результате.
Представьте себе асинхронную задачу, которая загружает изображение из Интернета и должна отображаться в контроллере представления. Если пользователь отходит от текущего контроллера представления, разработчик может реализовать код, который отправляет сообщение об отмене в imagePromise , который, в свою очередь, запускает обработчик ошибок, определенный Операцией HTTP-запроса, где запрос будет отменен.
В RXPromise сообщение об отмене будет пересылаться только от родителя его дочерним элементам, но не наоборот. То есть «корневое» обещание отменит все обещания детей. Но детское обещание отменяет только «ветку», где он является родителем. Сообщение об отмене также будет отправлено детям, если обещание уже выполнено.
Асинхронная задача может сама зарегистрировать обработчик для своего собственного обещания и, таким образом, может обнаружить, когда кто-то другой отменил его. Затем он может преждевременно прекратить выполнение, возможно, длительной и дорогостоящей задачи.
Вот пара других реализаций Promises в Objective-C, найденных на GitHub:
https://github.com/Schoonology/aplus-objc
https://github.com/affablebloke/deferred-objective-c
https://github.com/bww/FutureKit
https://github.com/jkubicek/JKPromises
https://github.com/Strilanc/ObjC-CollapsingFutures
https://github.com/b52/OMPromises
https://github.com/mproberts/objc-promise
https://github.com/klaaspieter/Promise
https: //github.com/jameswomack/Promise
https://github.com/nilfs/promise-objc
https://github.com/mxcl/PromiseKit
https://github.com/apleshkov/promises-aplus
https: // github.com/KptainO/Rebelle
и моя собственная реализация: RXPromise .
Этот список, вероятно, не полный!
При выборе третьей библиотеки для вашего проекта, пожалуйста, внимательно проверьте, соответствует ли реализация библиотеки предварительным условиям, перечисленным ниже:
Надежная библиотека обещаний ДОЛЖНА быть потокобезопасной!
Все дело в асинхронной обработке, и мы хотим использовать несколько процессоров и выполнять их в разных потоках одновременно, когда это возможно. Будьте осторожны, большинство реализаций не являются потокобезопасными!
Обработчики ДОЛЖНЫ вызываться асинхронно в отношении call-сайта! Всегда и ни на что!
Любая достойная реализация также должна следовать очень строгому шаблону при вызове асинхронных функций. Многие разработчики стремятся «оптимизировать» случай, когда обработчик будет вызываться синхронно когда обещание уже разрешено, когда обработчик будет зарегистрирован. Это может вызвать все виды проблем. Смотри Не отпускай Залго! ,
Должен также быть механизм отмены обещания.
Возможность отмены асинхронной задачи часто становится требованием с высоким приоритетом в анализе требований. Если нет, то наверняка будет подан запрос на улучшение от пользователя через некоторое время после выпуска приложения. Причина должна быть очевидна: любая задача, которая может остановиться или занять слишком много времени, должна быть отменена пользователем или по таймауту. Приличная библиотека обещаний должна поддерживать отмену.