【问题标题】:Support NSDocument changes in an external editor?支持在外部编辑器中更改 NSDocument?
【发布时间】:2014-02-10 22:11:58
【问题描述】:

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

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

如果我在外部编辑器中更改文件,我如何收到通知以便处理它?我假设有一些内置的东西,但我找不到它。

我正在寻找 NSDocument 中内置的东西。我知道 FSEvent,但对于大多数基于文档的应用程序来说,这似乎太低级了,无法做一些非常常见的事情。

【问题讨论】:

  • FSEvents 可以工作吗?
  • 寻找 NSDocument 内置的东西。有人在 NSFilePresenter 中提到了presentedItemDidChange(NSDocument 符合)。看看是否有一些东西可以显示 UI 来询问用户该做什么。
  • 根据文档,NSDocument should do this for you — 查看 - (NSDate *)fileModificationDate 的文档
  • @TonyArnold 这需要轮询,这对我来说似乎不是解决方案。

标签: objective-c macos cocoa nsdocument


【解决方案1】:

从 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: 或者您生成要写入磁盘的内容。

【讨论】:

    【解决方案2】:

    您想注册FSEvents API。从 10.7 开始,您可以观看任意文件。

    this question 的潜在重复。

    【讨论】:

    • 谢谢!这与其他问题有点不同。我只对文档内容的变化感兴趣。我希望 NSDocument 内置一些东西。
    【解决方案3】:

    当我在我的基于文档的应用程序中打开一个文档,在另一个应用程序中编辑,然后切换回我的应用程序时,您提到的相同方法 (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
    ]
    

    【讨论】:

      【解决方案4】:

      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 开销。

      【讨论】:

        猜你喜欢
        • 2013-08-29
        • 2016-02-14
        • 2017-12-30
        • 1970-01-01
        • 1970-01-01
        • 2015-04-03
        • 2012-08-19
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多