objective-cmacosnsdocumentnsfilecoordinator

NSDocument presentedItemDidChange called every second


I'm working on an NSDocument subclass. It represents a text file in a text editor.

I'm trying to use the NSFilePresenter protocol to respond to changes made by other applications (i.e., if the user saves a change in TextEdit while the same file is open here).

My current implementation works like this…

I add a property for a file coordinator:

@property (nonatomic) NSFileCoordinator *fileCoordinator;

I create it lazily:

- (NSFileCoordinator *) fileCoordinator {
    if (!_fileCoordinator) {
        [NSFileCoordinator addFilePresenter:self];
        _fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:self];
    }

    return _fileCoordinator;
}

When presentedItemDidChange is called, I reload the file from disk and display it:

- (void)presentedItemDidChange {
    [super presentedItemDidChange];

    NSLog(@"presentedItemDidChange was called");

    if (self.presentedItemURL.isFileURL && self.fileType) {
        NSError *coordinatorError = nil;

        [self.fileCoordinator coordinateReadingItemAtURL:self.presentedItemURL options:NSFileCoordinatorReadingWithoutChanges error:&coordinatorError byAccessor:^(NSURL *newURL) {
            NSError *readError = nil;
            [self readFromURL:newURL ofType:self.fileType error:&readError];
            if (readError) NSLog(@"%@", readError);
        }];

        if (coordinatorError) NSLog(@"%@", coordinatorError);

        [self reloadString];
    }
}

This code works: when I save a file in TextEdit, the changes appear in my app.

However, once I save this file, presentedItemDidChange is called repeatedly (about once per second). After a few minutes, the app crashes due to a memory error. No errors are logged; the console looks basically like this:

2016-02-17 22:43:46.233 MacDown[66847:2470964] presentedItemDidChange was called
2016-02-17 22:43:51.721 MacDown[66847:2470960] presentedItemDidChange was called
2016-02-17 22:43:52.816 MacDown[66847:2471206] presentedItemDidChange was called
2016-02-17 22:43:53.819 MacDown[66847:2470964] presentedItemDidChange was called
2016-02-17 22:43:54.920 MacDown[66847:2471206] presentedItemDidChange was called
2016-02-17 22:43:56.014 MacDown[66847:2470964] presentedItemDidChange was called
2016-02-17 22:43:57.115 MacDown[66847:2471206] presentedItemDidChange was called
2016-02-17 22:43:58.117 MacDown[66847:2470964] presentedItemDidChange was called

This is my first time using these APIs, so I assume I'm making a simple mistake. If it matters, I'm running OS X 10.11.3 and Xcode 7.2. What am I doing wrong?


Solution

  • presentedItemDidChange will be called not only when the file contents was changed but also when the metadata of the file was changed. And [NSDocument -readFromData:ofType:error:] overwrite the last opened date, one of the file metadata, of the file.

    So, this is the reason why presentedItemDidChange was called repeatedly. If you want to handle updated files in presentedItemDidChange, you normally need to determine what was changed at first.

    cf. NSFilePresenter Protocol Reference