Подтвердить что ты не робот

NSKeyedUnarchiver - нужна попытка/уловка?

Как я понимаю, использование блоков @try/@catch не рекомендуется, потому что exceptions следует бросать только на неустранимые, катастрофические ошибки (см. обсуждение с хорошим ответом @bbum: Обработка исключений в iOS).

Итак, я просмотрел свой код и нашел блок @try/@catch, который я не знаю, как избавиться от него:

NSData *fileData = [NSData dataWithContentsOfFile: ....];

NSDictionary *dictionary;

@try {
   dictionary = [NSKeyedUnarchiver unarchiveObjectWithData: fileData];
}
@catch (NSException *exception) {
   //....
}
@finally {
  //...
}

Проблема заключается в том, что (как указано в documentation) +unarchiveObjectWithData: вызывает NSInvalidArchiveOperationException, если NSData doesn 't содержать допустимый архив.

Поскольку данные предоставлены файлом, который пользователь выбрал, он не гарантирует, что он содержит действительный архив, и, таким образом, приложение будет разбиваться, если был выбран неправильный файл.

Теперь два вопроса:

  • Почему не +unarchiveObjectWithData: просто возвращает nil ( Изменить: и NSError**), если архив недействителен (это не похоже на катастрофическую или неустранимую ошибку).
  • Правилен ли шаблон выше (используя @try)? Я не нашел метода, который позволяет нам проверить, действительно ли данные содержат допустимый архив, и не нашли возможности обработать этот случай с использованием протокола делегата. Антитесь, я забыл?

Обратите внимание, что код, который, конечно, работает, я просто задаюсь вопросом, есть ли у него лучшая практика.

4b9b3361

Ответ 1

NSKeyedArchiver построен компанией Apple. Они управляют кодом, выполняемым во время выполнения unarchiveObjectWithData:, поэтому они также контролируют управление ресурсами во время обработки исключений (что является источником проблем за исключениями в Objective-C).

Если они могут гарантировать, что между вашим вызовом unarchiveObjectWithData: и точкой в ​​коде, где они создают исключение, нет никакого внешнего кода (ни третьей стороны, ни вашего кода приложения), теоретически возможно безопасно использовать исключение, до тех пор, пока код вызова позаботится о правильной очистке.

Проблема заключается в том, что это предположение может быть не так: обычно используется NSKeyedArchiver для сериализации пользовательских объектов. Обычно пользовательский класс реализует initWithCoder: для чтения данных классов (используя методы архиватора, такие как decodeObjectForKey:).

Если архиватор выбрасывает исключение в одном из этих методов, нет способа исправить обработку ресурсов для архиватора. Исключение будет выбрано через пользовательский объект initWithCoder:. Архиватор не знает, есть ли что-нибудь для очистки, чем десериализованные объекты. Таким образом, в этом случае возникновение исключения означает, что процесс находится в опасном состоянии, и может возникнуть нежелательное поведение.

Относительно ваших вопросов:

Почему [NSKeyedArchiver не использует правильную обработку ошибок Cocoa]?

Только инженеры Apple, которые построили архиватор, знают. Я предполагаю, что обработка исключений и архивирование с ключом были построены примерно в одно и то же время (около 2001 года), и в этот момент еще не было ясно, что обработка исключений никогда не будет гражданином первого класса в Objective-C.

Правильно ли шаблон @try?

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

Тем не менее, очень сложно добиться полной корректности. Вы должны убедиться, что все исполняемые коды знают об исключениях и правильно ли их очищают.

ARC, например не выполняет очистку исключений для локальных переменных и временных объектов по умолчанию (вам нужно включить -fobjc-arc-exceptions для этого).

Кроме того, нет документации по безопасности исключений для аксессуаров свойств @synthesized (когда atomic они могут протечь блокировку).

Вывод:

Существует множество тонких способов того, как исключения могут разрушать вещи. Это сложно и требует глубокого знания реализации всех задействованных частей для создания безопасного кода исключения в Objective-C.

Все это приводит к заключению. Если вы хотите обработать ошибки изящно при загрузке, возможно, поврежденных архивов и продолжить нормальное выполнение впоследствии: Не использовать NSKeyedArchiver.

Ответ 2

В iOS 9 был добавлен новый метод NSKeyedUnarchiver, который теперь возвращает ошибку:

Swift:

public class func unarchiveTopLevelObjectWithData(data: NSData) throws -> AnyObject?

Objective-C:

+ (nullable id)unarchiveTopLevelObjectWithData:(NSData *)data error:(NSError **)error;

Однако это не обратно совместимо с предыдущими версиями iOS, поэтому вам нужно проверить доступность рамок.