支持外部编辑器中的NSDocument更改?

14

我有一个带有一些简单代码的NSDocument:

- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError {
  self.string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  return YES;
}

如果我在外部编辑器中更改文件,我该如何收到通知以便处理它?我假设有一些内置的功能可以解决这个问题,但我找不到它。

我正在寻找的是嵌入在NSDocument中的解决方案。我知道FSEvent,但对于大多数基于文档的应用程序来说,它似乎太底层了。


FSEvents 可以工作吗? - keegan3d
可能是文件系统更改的通知?的重复问题。 - user529758
寻找内置于NSDocument中的内容。有人提到了NSFilePresenter中的presentedItemDidChange(NSDocument符合该协议)。但是我想知道是否有一些东西可以显示UI,询问用户要做什么。 - Sam Soffes
根据文档,NSDocument应该为您完成此操作 - 查看“- (NSDate *)fileModificationDate”文档。 - Tony Arnold
@TonyArnold 这将需要轮询,这对我来说似乎不是一个解决方案。 - trojanfoe
@trojanfoe - 根据文档,警报应该无需轮询即可呈现给用户。我相信Sam已经检查过了,但它似乎并没有像文档中描述的那样工作。 - Tony Arnold
4个回答

4

自从 OS X v10.7 以来,NSDocument 提供了一种更简单的机制,您可以在子类中重写:-presentedItemDidChange

处理 -presentedItemDidChange,忽略元数据更改

但是,仅依靠此回调可能会产生误报,特别是当元数据更改时。例如,对于存储在 Dropbox 中的文件,这很快会让我感到恼火。

我的解决方案是在 Swift 中通常是这样的:

class MyDocument: NSDocument {
    // ...

    var canonicalModificationDate: Date!

    override func presentedItemDidChange() {

        guard fileContentsDidChange() else { return }

        guard isDocumentEdited else {
            DispatchQueue.main.async { self.reloadFromFile() }
            return
        }

        DispatchQueue.main.async { self.showReloadDialog() }
    }

    fileprivate func showReloadDialog() {

        // present alert "do you want to replace your stuff?"
    }

    /// - returns: `true` if the contents did change, not just the metadata.
    fileprivate func fileContentsDidChange() -> Bool {

        guard let fileModificationDate = fileModificationDateOnDisk()
            else { return false }

        return fileModificationDate > canonicalModificationDate
    }

    fileprivate func fileModificationDateOnDisk() -> Date? {

        guard let fileURL = self.fileURL else { return nil }

        let fileManager = FileManager.default
        return fileManager.fileModificationDate(fileURL: fileURL)
    }
}

现在您还需要更新子类中的canonicalModificationDate

  • 在来自“是否要替换内容”的警告的回调中,我称之为-ignoreLatestFileChanges,以便您不会一直打扰用户;
  • -readFromURL:ofType:error:或者您用于读取初始值的其他方法中;
  • -dataOfType:error:或者您用于生成写入磁盘内容的其他方法中。

1
当我在我的基于文档的应用程序中打开文档,使用另一个应用程序进行编辑并切换回我的应用程序时,与您提到的相同方法(readFromData:ofType:error:)将使用新数据调用。当您从版本浏览器恢复先前的版本时,也会调用此方法。
然后,您可以添加一个布尔实例变量来检查它是否因为外部更新而被调用(在我的情况下,我检查我的IBOutlets之一是否初始化:如果没有,文档是第一次加载)。如果文档已经初始化,您可能需要将使用string实例变量的代码移动到某个方法中,以便您可以调用该方法,如下所示:
- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError {
    self.string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    if (self.isLoaded)
        [self documentChanged];
    return YES;
}

- (void)windowControllerDidLoadNib:(FCWindowController *)windowController {
    self.isLoaded = YES;
    [self documentChanged];
}

- (void)documentChanged {
    // use self.string as you like
]

1

谢谢!这与其他问题有些不同。我只对文档内容的更改感兴趣。我希望NSDocument内置了一些功能。 - Sam Soffes

0

NSMetadataQuery似乎是在不轮询且低CPU开销的情况下监视文件和文件夹变化的最佳方法。

一些基本的用于监视文件夹的代码,您只需将filePattern设置为文件名而不是通配符*。

NSString* filePattern = [NSString stringWithFormat:@"*"];
NSString *watchedFolder = @"not/fake/path";

NSMetadataQuery *query = [[NSMetadataQuery alloc] init];
[query setSearchScopes:@[watchedFolder]];
NSString *itemName = (NSString*)kMDItemFSName;

[query setPredicate:[NSPredicate predicateWithFormat:@"%K LIKE %@", NSMetadataItemDisplayNameKey, filePattern]];

NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:@selector(queryFoundStuff:) name:NSMetadataQueryDidFinishGatheringNotification object:query];
[nc addObserver:self selector:@selector(queryFoundStuff:) name:NSMetadataQueryDidUpdateNotification object:query];
[query setNotificationBatchingInterval:0.5];
[query startQuery];

- (void)queryFoundStuff:(NSNotification *)notification {
    [query disableUpdates];
    NSLog(@"Notification: %@", notification.name);
    NSMutableArray *results = [NSMutableArray arrayWithCapacity:query.resultCount];

    for (NSUInteger i=0; i<query.resultCount; i++) {
      [results addObject:[[query resultAtIndex:i] valueForAttribute:NSMetadataItemPathKey]];
    }

    // file has updated, do something 

    [query enableUpdates];
}

我从未找到过一个理想的解决方案来监视文件更新,NSFilePresenter听起来应该是适当的高级解决方案,但据我所知,它只在使用NSFilePresenter的另一个应用程序正在编辑文件时才起作用。我还尝试过VDKQueue和SCEvents,它们包装了低级内核事件,但会有CPU开销。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接