UIDocument & NSFileWrapper - большие файлы, требующие длительного хранения, несмотря на дополнительные изменения

118
7

У меня есть приложение на основе UIDocument, которое использует NSFileWrapper для хранения данных. Файловая оболочка "мастер" содержит множество дополнительных файлов каталога, каждая из которых представляет собой другую страницу документа.


При сохранении большого документа, для которого была изменена только небольшая часть одной страницы, UIDocument тратит время LONG в фоновом режиме, записывая изменения (в writeContents:andAttributes:safelyToURL:forSaveOperation:error:). Разумеется, это должно быть написано только одно небольшое изменение в файловой оболочке... что занимает так много времени?


My contentsForType:error: override возвращает новую оболочку файла каталога с содержимым обертки главного файла (à la WWDC 2012 Session 218 - Использование iCloud с UIDocument):


- (id)contentsForType:(NSString *)typeName error:(NSError *__autoreleasing *)outError
{
if (!_fileWrapper) {
[self setupEmptyDocument];
}
return [[NSFileWrapper alloc] initDirectoryWithFileWrappers:[_fileWrapper fileWrappers]];
}

И вот прекрасная картина трассировки стека от Time Profiler:


UIDocument slow write stack trace


Кстати, в этом рабочем потоке говорится ~ 1.6s - в фактическом времени выполнения это равно примерно 8 секундам.


Edit:


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


Edit:


У меня была дополнительная игра с примером приложения CloudNotes, и оказалось, что NSFileWrapper реализует инкрементное сбережение, по крайней мере, в этом случае! Я тестировал его, инициализируя документ со 100 нотами, каждый из которых содержал около 5 МБ данных. Я сделал небольшое редактирование здесь и там (одно изменение символа в текстовом представлении документирует документ как необходимый для сохранения), и записывается примерно то, как долго сохранялось каждое сохранение. Тест относительно грубый (и работает на симуляторе), но результаты были примерно такими:


    1st write: ~ 8000ms
    Вторая запись: ~ 4000 мс
    Третья запись: ~ 300 мс
    все последующие записи: ~ 40 мс

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


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

спросил(а) 2021-01-19T12:54:15+03:00 6 месяцев, 2 недели назад
1
Решение
77

NSFileWrapper фактически загружает весь документ в память. Таким образом, с UIDocument использование NSFileWrapper на самом деле не подходит для больших документов. Документация заставляет вас думать, что она обеспечивает инкрементное сохранение, но в моем случае это, похоже, не так.


UIDocument не ограничивается только NSFileWrapper или NSData. Вы можете использовать свой собственный пользовательский класс, вам просто нужно переопределить определенные методы. Я закончил писать свой собственный класс оболочки файлов, который просто ссылается на файлы на диске и читает/записывает отдельные файлы по запросу.


Вот как выглядит класс UIDocument с помощью специальной оболочки файла:

@implementation LSDocument

- (BOOL)writeContents:(LSFileWrapper *)contents
andAttributes:(NSDictionary *)additionalFileAttributes
safelyToURL:(NSURL *)url
forSaveOperation:(UIDocumentSaveOperation)saveOperation
error:(NSError *__autoreleasing *)outError
{
return [contents writeUpdatesToURL:self.fileURL error:outError];
}

- (BOOL)readFromURL:(NSURL *)url error:(NSError *__autoreleasing *)outError
{
__block LSFileWrapper *wrapper = [[LSFileWrapper alloc] initWithURL:url isDirectory:NO];
__block BOOL result;
dispatch_sync(dispatch_get_main_queue(), ^(void) {
result = [self loadFromContents:wrapper
ofType:self.fileType
error:outError];
});
[wrapper loadCache];
return result;
}

@end


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

ответил(а) 2021-01-19T12:54:15+03:00 6 месяцев, 2 недели назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема