Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSFileWrapper returns nil, sometimes

I'm using NSFileWrapper for my package document. Sometimes, when I request the data of a file inside the package I get nil.

This is how I query the data of a file inside the package:

- (NSData*) dataOfFile(NSString*)filename {
    NSFileWrapper *fileWrapper = [self.documentFileWrapper.fileWrappers objectForKey:filename];
    return fileWrapper.regularFileContents; // This returns nil sometimes. Why?
}

This method eventually starts returning nil for some files (not all). Sadly, I haven't managed to reproduce the problem consistently.

In case it helps, this is how I open the package:

- (BOOL) readFromFileWrapper:(NSFileWrapper *)fileWrapper ofType:(NSString *)typeName error:(NSError *__autoreleasing *)outError {
    self.documentFileWrapper = fileWrapper;
    return YES;
}

This is how I update the data of a file inside the package:

- (void) updateFile:(NSString*)filename withData:(NSData*)data {
    SBFileWrapper *fileWrapper = [self.documentFileWrapper.fileWrappers objectForKey:filename];
    if (fileWrapper) {
        [self.documentFileWrapper removeFileWrapper:fileWrapper];
    }
    NSFileWrapper *fileWrapper = [[SBFileWrapper alloc] initRegularFileWithContents:data ];
    fileWrapper.preferredFilename = filename;
    [self.documentFileWrapper addFileWrapper:fileWrapper];
}

This is how I save the package:

- (NSFileWrapper*) fileWrapperOfType:(NSString *)typeName error:(NSError *__autoreleasing *)outError {
    return self.documentFileWrapper;
}

Why can this be happening? Is there a way to prevent it?

The documentation of regularFileContents appears to talk about this problem:

This method may return nil if the user modifies the file after you call readFromURL:options:error: or initWithURL:options:error: but before NSFileWrapper has read the contents of the file. Use the NSFileWrapperReadingImmediate reading option to reduce the likelihood of that problem.

But I don't understand what has to be changed in the code above to prevent this situation.

Failed Experiments

I tried saving the document if regularFileContents return nil but it still returns nil afterwards. Like this:

- (NSData*) dataOfFile(NSString*)filename {
    NSFileWrapper *fileWrapper = [self.documentFileWrapper.fileWrappers objectForKey:filename];
    NSData *data = fileWrapper.regularFileContents;
    if (!data) {
            [self saveDocument:nil];
            fileWrapper = [self.documentFileWrapper.fileWrappers objectForKey:filename];
            data = fileWrapper.regularFileContents;
    }
    return data;
}
like image 308
hpique Avatar asked Oct 16 '12 09:10

hpique


2 Answers

There is not enough code to see what's really going on. However the root cause is that NSFileWrapper is just what its name implies: an object that represents a file or directory. Therefore, the actual file or directory can easily get "out of synch" with the object, which lives in memory. Whenever NSFileWrapper determines that this has occurred, it returns nil for certain operations. The solution is to make NSFileWrapper objects short-lived. Create and open just when you need them and then save and close as soon as possible.

In particular, it looks like your code is keeping a pointer to a package directory wrapper around for a long time and assuming that it's always valid. If the directory changes for any reason, this isn't the case. Recode so that you get a fresh package directory wrapper each time you need it, and the problem ought to go away.

like image 90
Gene Avatar answered Oct 29 '22 21:10

Gene


If the file changes on disk then you'll get nil (as @Gene says). However, you can check this by using matchesContentsOfURL: method which:

determines whether a disk representation may have changed, based on the file attributes stored the last time the file was read or written. If the file wrapper’s modification time or access permissions are different from those of the file on disk, this method returns YES. You can then use readFromURL:options:error:

This from Working with File Wrappers Apple documentation.

Note this from the intro to that section:

Because the purpose of a file wrapper is to represent files in memory, it’s very loosely coupled to any disk representation. A file wrapper doesn’t record the path to the disk representation of its contents. This allows you to save the same file wrapper with different URLs, but it also requires you to record those URLs if you want to update the file wrapper from disk later.

So you'll have to save the url to the original file if you want/need to re-read it.

Be interesting to hear what matchesContentsofURL: returns when you're seeing nil results.

like image 32
Dad Avatar answered Oct 29 '22 23:10

Dad