I have a UIDocument
based app that uses NSFileWrapper
s to store data. The 'master' file wrapper contains many additional directory file wrappers, each of which represents a different page of the document.
When saving a large document for which only a small proportion of one page has been modified, UIDocument
spends a LONG time in the background writing the changes (in writeContents:andAttributes:safelyToURL:forSaveOperation:error:
). Surely it should only be writing out this one small change to the file wrapper... what's taking so long?
My contentsForType:error:
override returns a new directory file wrapper with the contents of the master file wrapper (à la WWDC 2012 Session 218 - Using iCloud with UIDocument):
- (id)contentsForType:(NSString *)typeName error:(NSError *__autoreleasing *)outError
{
if (!_fileWrapper) {
[self setupEmptyDocument];
}
return [[NSFileWrapper alloc] initDirectoryWithFileWrappers:[_fileWrapper fileWrappers]];
}
And here's a lovely picture of a stack trace from Time Profiler:
Incidentally, it says ~1.6s in that worker thread to save - in actual run time this equated to about 8 seconds.
Edit:
Is there some way I can check whether the file wrappers require writing to disk or not? Just so I can confirm that I'm not somehow doing something strange like updating every sub file wrapper when I make a small change (although I'm sure I'm not...).
Edit:
I had a further play around with the CloudNotes sample app, and it appears that NSFileWrapper
does implement incremental saving, at least in that case! I tested it by initialising a document with 100 notes, each of which contained about 5MB of data. I did a small edit here and there (a single character change to a text view flags the document as needing saving), and recorded roughly how long each save took. The test is relatively crude (and run on the simulator), but the results were something like this:
Obviously there are many factors affecting the time it takes, especially since it's saving using file coordination in a background thread, but in general the trend always seems to be this sort of exponential decay, until all writes become really very fast.
But I'm still trying to figure out why this doesn't happen in my app. For a large multi-page document (large, but still many times smaller than the document for the CloudNotes test I performed above) the user can be waiting many seconds for a document to close. I don't want to have to put a spinner up for something that should be practically instantaneous.
NSFileWrapper
is actually loading the entire document into memory. So with a UIDocument
, using an NSFileWrapper
is actually not good for large documents. The documentation makes you think it does incremental saving, but in my case it didn't seem to do that.
UIDocument
isn't restricted to just NSFileWrapper
or NSData
. You can use your own custom class, you just have to override certain methods. I ended up writing my own file wrapper class that simply refers to files on disk and reads/writes individual files on-demand.
This is what my UIDocument
class looks like using the custom file wrapper:
@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
I use this as a base class and subclass it for other projects. It should give you an idea of what you have to do to integrate a custom file wrapper class.
I know this is a super old thread, but to help future travelers: In my case, I had a subdirectory NSFileWrapper which was not incrementally saving.
I found that if you make a copy of an NSFileWrapper, you need to set the copy's filename, fileAttributes (and possibly preferredFilename) to the original's in order for the save to be incremental. After copying those over, the contents of the subfolder would incrementally save (i.e. only write if replaced with new NSFileWrappers).
Note to Apple: Seriously, the whole NSFileWrapper API is a mess and should be cleaned up.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With