NSFilePresenter methods never get called
Asked Answered
M

2

8

I'm trying to write a simple (toy) program that uses the NSFilePresenter and NSFileCoordinator methods to watch a file for changes.

The program consists of a text view that loads a (hardcoded) text file and a button that will save the file with any changes. The idea is that I have two instances running and saving in one instance will cause the other instance to reload the changed file.

Loading and saving the file works fine but the NSFilePresenter methods are never called. It is all based around a class called FileManager which implements the NSFilePresenter protocol. The code is as follows:

Interface:

@interface FileManager : NSObject <NSFilePresenter>
@property (unsafe_unretained) IBOutlet NSTextView *textView;

- (void) saveFile;
- (void) reloadFile;

@end

Implementation:

@implementation FileManager
{
    NSOperationQueue* queue;
    NSURL* fileURL;
}

- (id) init {
    self = [super init];
    if (self) {
        self->queue = [NSOperationQueue new];
        self->fileURL = [NSURL URLWithString:@"/Users/Jonathan/file.txt"];
        [NSFileCoordinator addFilePresenter:self];
    }
    return self;
}

- (NSURL*) presentedItemURL {
    NSLog(@"presentedItemURL");
    return self->fileURL;
}

- (NSOperationQueue*) presentedItemOperationQueue {
    NSLog(@"presentedItemOperationQueue");
    return self->queue;
}

- (void) saveFile {
    NSFileCoordinator* coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:self];
    NSError* error;
    [coordinator coordinateWritingItemAtURL:self->fileURL options:NSFileCoordinatorWritingForMerging error:&error byAccessor:^(NSURL* url) {
        NSString* content = [self.textView string];
        [content writeToFile:[url path] atomically:YES encoding:NSUTF8StringEncoding error:NULL];
    }];
}

- (void) reloadFile {
    NSFileManager* fileManager = [NSFileManager defaultManager];
    NSFileCoordinator* coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:self];
    NSError* error;
    __block NSData* content;
    [coordinator coordinateReadingItemAtURL:self->fileURL options:0 error:&error byAccessor:^(NSURL* url) {
        if ([fileManager fileExistsAtPath:[url path]]) {
            content = [fileManager contentsAtPath:[url path]];
        }
    }];
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.textView setString:[[NSString alloc] initWithData:content encoding:NSUTF8StringEncoding]];
    });
}

// After this I implement *every* method in the NSFilePresenter protocol. Each one
// simply logs its method name (so I can see it has been called) and calls reloadFile
// (not the correct implementation for all of them I know, but good enough for now).

@end

Note, reloadFile is called in applicationDidFinishLaunching and saveFile gets called every time the save button is click (via the app delegate).

The only NSFilePresenter method that ever gets called (going by the logs) is presentedItemURL (which gets called four times when the program starts and loads the file and three times whenever save is clicked. Clicking save in a second instance has no noticeable effect on the first instance.

Can anyone tell me what I'm doing wrong here?

Merocrine answered 27/3, 2013 at 22:26 Comment(0)
C
5

I was struggling with this exact issue for quite a while. For me, the only method that would be called was -presentedSubitemDidChangeAtURL: (I was monitoring a directory rather than a file). I opened a technical support issue with Apple, and their response was that this is a bug, and the only thing we can do right now is to do everything through -presentedSubitemDidChangeAtURL: if you're monitoring a directory. Not sure what can be done when monitoring a file.

I would encourage anyone encountering this issue to file a bug (https://bugreport.apple.com) to encourage Apple to get this problem fixed as soon as possible.

Chirr answered 13/10, 2013 at 9:47 Comment(0)
C
2

(I realize that this is an old question, but... :) )

First of all, I notice you don't have [NSFileCoordinator removeFilePresenter:self]; anywhere (it should be in dealloc).

Secondly, you wrote:

    // After this I implement *every* method in the NSFilePresenter protocol. Each one
    // simply logs its method name (so I can see it has been called) and calls reloadFile
    // (not the correct implementation for all of them I know, but good enough for now).

You're right: it's the incorrect implementation! And you're wrong: it's not good enough, because it's essential for methods like accommodatePresentedItemDeletionWithCompletionHandler: which take a completion block as a parameter, that you actually call this completion block whenever you implement them, e.g.

    - (void) savePresentedItemChangesWithCompletionHandler:(void (^)(NSError * _Nullable))completionHandler
    {
        // implement your save routine here, but only if you need to!
        if ( dataHasChanged ) [self save]; // <-- meta code
        //
        NSError * err = nil; // <-- = no error, in this simple implementation
        completionHandler(err); // <-- essential!
    }

I don't know whether this is the reason your protocol methods are not being called, but it's certainly a place to start. Well, assuming you haven't already worked out what was wrong in the past three years! :-)

Courtesan answered 15/6, 2016 at 20:29 Comment(2)
"it should be in dealloc" Is that right? Won't that be a case of a cyclic reference? In that dealloc won't be called while the thing is registered with NSFileCoordinator?Schuss
"While you may register a file presenter in its initialization method, you do not unregister a file presenter in its deallocation method. Because NSFileCoordinator retains registered file presenter objects, the system does not deallocate the file presenter until you unregister it, even when your own code no longer retains it." (ref)Schuss

© 2022 - 2024 — McMap. All rights reserved.