UIDocument & NSFileWrapper - large files that require long-term storage, despite additional changes

I have a UIDocument based UIDocument that uses NSFileWrapper to store data. The file wrapper "master" contains many additional catalog files, each of which represents a different page in the document.

When saving a large document for which only a small part of one page has been changed, the UIDocument spends LONG time in the background, writing the changes (in writeContents:andAttributes:safelyToURL:forSaveOperation:error: . Of course, this should only be written one small change in the file shell ... what takes so much time?

My contentsForType:error: override returns a new directory file wrapper with the contents of the main 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 great picture of the stack trace from Time Profiler:

UIDocument slow write stack trace

By the way, this workflow says ~ 1.6s - in the actual runtime, this is about 8 seconds.


Edit:

Is there any way to check if it is necessary to write to disk to wrap files or not? I can simply confirm that I am not doing something strange, like updating every subfile wrapper when I make a small change (although I am sure that I am not ...).


Edit:

I had an additional game with the CloudNotes application example, and it turned out that NSFileWrapper implements incremental savings, at least in this case! I tested it by initializing a document with 100 notes, each of which contained about 5 MB of data. I did a little editing here and there (one change in the character in the text view documents the document as necessary for saving), and it records about how long each saving was saved. The test is relatively crude (and works on a simulator), but the results were something like this:

  • 1st write: ~ 8000ms
  • Second record: ~ 4000 ms
  • Third record: ~ 300 ms
  • all subsequent entries: ~ 40 ms

Obviously, there are many factors that influence the time it takes, especially because it saves using file coordination in the background stream, but overall the trend always seems to be such an exponential decline until all records become very fast.

But I'm still trying to understand why this is not happening in my application. For a large multi-page document (large, but still many times smaller than the document for the CloudNotes test, which I completed above), the user can wait many seconds to close the document. I do not want to apply a spinner for something that should be almost instantaneous.

+8
source share
2 answers

NSFileWrapper actually loads the entire document into memory. Thus, with UIDocument using NSFileWrapper is not really suitable for large documents. The documentation makes you think that it provides incremental persistence, but in my case this doesn't seem to be the case.

UIDocument not limited to NSFileWrapper or NSData . You can use your own custom class, you just need to override certain methods. I ended up writing my own file shell class, which simply refers to files on disk and reads / writes individual files on request.

Here's what the UIDocument class looks like using a special 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 a subclass for other projects. It should give you an idea of ​​what you need to do to integrate your own wrapper class.

+4
source

I know this is a very old topic, but to help future travelers: in my case, I had an NSFileWrapper subdirectory that was not saved gradually.

I found that if you make a copy of NSFileWrapper, you need to set the copy file name, fileAttributes (and possibly the preferred file name) to the original so that the save is incremental. After copying them, the contents of the subfolder will be gradually saved (i.e., recorded only if replaced with new NSFileWrappers).

Note to Apple: Seriously, the entire NSFileWrapper API is a mess and needs to be cleaned out.

0
source

All Articles