Расширение на Jörn Eyrich отвечает (повышайте его ответ, если вы продвигаете этот вариант), если у вас нет контроля над вызовами dispatch_async
для ваших блоков, как это может быть в случае блоков завершения асинхронизации, вы можете использовать GCD группы, использующие dispatch_group_enter
и dispatch_group_leave
.
В этом примере мы притворяемся, что computeInBackground
- это то, что мы не можем изменить (представьте, что это обратный вызов делегата, NSURLConnection completeHandler или что-то еще) и таким образом, у нас нет доступа к вызовам отправки.
// create a group
dispatch_group_t group = dispatch_group_create();
// pair a dispatch_group_enter for each dispatch_group_leave
dispatch_group_enter(group); // pair 1 enter
[self computeInBackground:1 completion:^{
NSLog(@"1 done");
dispatch_group_leave(group); // pair 1 leave
}];
// again... (and again...)
dispatch_group_enter(group); // pair 2 enter
[self computeInBackground:2 completion:^{
NSLog(@"2 done");
dispatch_group_leave(group); // pair 2 leave
}];
// Next, setup the code to execute after all the paired enter/leave calls.
//
// Option 1: Get a notification on a block that will be scheduled on the specified queue:
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@"finally!");
});
// Option 2: Block an wait for the calls to complete in code already running
// (as cbartel points out, be careful with running this on the main/UI queue!):
//
// dispatch_group_wait(group, DISPATCH_TIME_FOREVER); // blocks current thread
// NSLog(@"finally!");
В этом примере computeInBackground: завершение: реализовано как:
- (void)computeInBackground:(int)no completion:(void (^)(void))block {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@"%d starting", no);
sleep(no*2);
block();
});
}
Выход (с отметками времени от run):
12:57:02.574 2 starting
12:57:02.574 1 starting
12:57:04.590 1 done
12:57:06.590 2 done
12:57:06.591 finally!
Самое главное, чтобы ваш абонент получил информацию о том, что вызов createUnit()
не удался. Вы уже сказали, что null
- плохой выбор для этого, и я полностью согласен.
Лучший способ - создать исключение для вашего абонента, но какое именно?
Оригинальное?
Или вновь -created one?
Какие аспекты мы должны рассмотреть?
Итак, единственная причина, по которой я вижу обертку исключения более низкого уровня, в то, что вы хотите добавить контекстную информацию, полезную для ведения журнала (или если какая-то сигнатура фиксированного метода вынуждает вас переводить оригинальное исключение).
Создайте новый соответствующий Exception
, который может управлять пользовательскими данными, такими как значение ввода unitLevel
.
public class UnitCreationException extends Exception {
...
// Package-private constructor.
// Don't expose too much to the outer world
UnitCreationException(
final String message,
final int unitLevel,
final Throwable cause) { ... }
}
Поймайте Exception
на месте, записав, что, по вашему мнению, может оказаться полезной информацией, доступ к которой можно получить только в этот момент
public Unit createUnit(final int unitLevel) throws UnitCreationException {
final List<String> widgetClassNames = getUnitsClasses();
try {
return (Unit) Class.forName(widgetClassNames.get(unitLevel))
.getConstructor(Player.class)
.newInstance(getOwner().get());
} catch (InstantiationException
| IllegalAccessException
| IllegalArgumentException
| InvocationTargetException
| NoSuchMethodException
| SecurityException
| ClassNotFoundException e) {
// Do not log the Exception stack-trace here, you'll do that
// at a hihger level
logger.error("Error message");
// Re-throw your custom and more meaningful Exception
throw new UnitCreationException("Error message", unitLevel, e);
}
}
На более высоком уровне вы будете вынуждены чтобы понять, что UnitCreationException
try {
final Unit unit = createUnit(10);
} catch (final UnitCreationException e) {
// Here you can log the Exception stack-trace
logger.error("Error message", e);
// Do something with the information contained in the custom Exception
final int erroredUnitLevel = e.getUnitLevel();
...
}
Всегда были споры о проверенных Exception
с против RuntimeException
с. Я считаю, что это хороший вариант использования для проверенного, особенно если это часть библиотеки, которая может использоваться несколькими клиентами.
Вы хотели бы иметь возможность каким-то образом восстановиться после такой ошибки, не останавливая все приложение (возможно, с помощью логики повторной попытки).
Я был свидетелем того, как разработчики использовали Optional<T>
в качестве возвращаемого типа, только чтобы избежать null
. Это контрпродуктивно , так как вы теряете действительную причину ошибки, и вы не можете отреагировать на это в своей собственной ветви (ветка catch
)
В конечном счете, Java предлагает вам способ настройки Exception
, так что используйте эту функцию.