从 Launch Services 隐藏 NSDocument 子类

Hide NSDocument subclass from Launch Services

我有一对基本上是双胞胎的应用程序 - 一个是客户端,另一个是服务器。它们共享许多相同的代码,并且都使用相同的 NSDocument subclass 来实现它们共享的文档格式。客户端应用程序有一个用户界面,允许用户可视化地处理文档,但服务器应用程序没有(尽管它 运行 作为常规应用程序,而不是守护程序),它被设计为 运行 看不见。

问题是当文档放在它的图标上时会触发服务器应用程序。如果服务器应用程序 运行ning 而客户端应用程序不是 运行ning,并且用户双击文档,也会触发服务器应用程序。在那种情况下,我想要发生的是启动服务启动客户端应用程序并打开文档,而是尝试使用服务器应用程序打开文档。我已经自定义了 NSApplicationDelegate application:openFile: 方法,以便服务器应用程序在那种情况下拒绝实际打开文档,但我想要的是 NSApplicationDelegate 不使用文档打开事件调用它。这让用户感到困惑,因为他们希望双击文档打开客户端应用程序,无论服务器应用程序是否 运行ning。

Apple 的 Core Foundation Keys 文档似乎表明这样做的方法是使用 LSHandlerRank 属性.

https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/TP40009249-SW1

对于客户端应用程序,我已将其设置为 "Owner"。

<key>LSHandlerRank</key>
<string>Owner</string>

对于服务器应用程序,我将其设置为 "None"。

<key>LSHandlerRank</key>
<string>None</string>

不幸的是,这没有效果。如果客户端应用程序未 运行ning.

,我仍然可以将文档放在服务器应用程序图标上,或者双击文档以将服务器应用程序向前移动

我认为可能有前途的另一个 属性 是 CFBundleTypeRole。客户端应用程序的 plist 将其设置为 "Editor".

<key>CFBundleTypeRole</key>
<string>Editor</string>

此 属性 的文档确实很少,但它确实说 "None" 是一个选项。所以我在服务器应用程序 plist 中尝试了它,然后我无法再以编程方式打开我的 NSDocument 文件。另一方面,将文档放在服务器应用程序图标上 still 导致图标亮起,因此 Launch Services 显然仍然认为服务器应用程序可以处理此类文件。

总而言之,我需要对 plist 进行一些更改,以便我仍然可以以编程方式处理与我的 NSDocument 子 class 关联的文件,但我不希望 Launch Services 知道我的 (服务器)应用程序可以使用这些文件。这可能吗?

----跟进Peter Hosey的回答----

感谢您的回答。我不知道 lsregister shell 命令,它看起来非常方便。转储在我的机器上产生了 46 兆字节的数据!

不过,我很确定这不是缓存问题。虽然我的电脑有多个服务器应用程序副本,但这个问题是由客户发现的。他只有一个应用程序副本,而且他只用了很短的时间。我已经一年多没有更改 plist 内容了,所以他的系统上不会有任何缓存。

你建议不要使用Launch Services,但我没有说清楚,我没有利用它,也不希望Launch Services打开我的文档(至少不是我的应用程序的服务器版本).事实上,我已经成功地修改了应用程序,如果 Launch Services 确实请求服务器应用程序打开文档,它会忽略此请求。正在通过内部 TCP/IP 服务器打开文档,该服务器使用 openDocumenWithContentsOfURL,如下所示:

[sharedDocumentController openDocumentWithContentsOfURL:databaseURL display:openWindows error:&err];

openDocumenWithContentsOfURL 方法似乎需要为文档类型设置 plist。我的问题是,这也告诉 Finder 这个应用程序可以处理我不想要的这种类型的文档。因此,我正在寻找一些可以在应用程序中使用 NSDocument 的方法,但不会将其公开给 Finder(因此我认为不会将其公开给 Launch Services)。也许那是不可能的。

是否有一些方法可以打开没有 URL 的文档,这样扩展名是什么或 plist 如何设置都无关紧要?我在 NSDocumentController class 中没有看到任何此类记录的方法。在我看来,测试似乎证实了 NSDocument class 依赖于 plist 到 link 文件扩展到 NSDocument subclasses.

如果有办法更改 plist 来执行此操作,那么我肯定需要使用 lsregister 清除缓存来测试它:)

使用 LS 的挑战之一是缓存失效(众所周知,这是计算机科学中两个最难的问题之一,另外两个是命名问题和差一错误)。 LS 出于性能原因希望缓存所有内容,因此当您更改某些内容时,确保从 LS 的数据库中清除旧信息变得很重要。

首先要检查的是您有多少服务器应用程序副本。如果您曾经将它从构建产品文件夹中复制出来,那么旧副本可能就是 LS 试图用来处理文档的那个。

要检查的第二件事是 LS 认为您拥有哪些服务器应用程序副本,以及它认为它们可以处理什么。

使用 lsregister -dump 可以做到这两点。 lsregister 位于 /System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister,它的转储将为您提供有关已注册哪些类型 (UTI)、已注册哪些应用程序包(包括不同的具有不同功能的不同位置的版本)、服务等

lsregister -help 将告诉您它的其他选项,其中一些可能有助于从 LS 的数据库中删除 outdated/unhelpful 记录。

我可能会避免为此使用 LS。可能会在服务端app的Info.plist中导入类型,但不要将其列为文档类型,也不要使用客户端的LS用服务端app打开文档。

相反,使用 NSAppleEventDescriptor 创建您自己的 Open Documents 事件,然后使用 AESendMessage 将其发送到服务器应用程序。事件 class 是 kCoreEventClass,事件 ID 是 kAEOpenDocuments。目标描述符应该是 typeApplicationBundleID 类型的 NSAppleEventDescriptor,包含服务器应用程序的包 ID 作为字符串。

将事件的 keyDirectObject 关键字参数设置为至少一个 typeFileURL 描述符的列表描述符,其中包含要打开的文档的 URL(也表示为字符串)。

哦,所以你没有使用 Launch Services(或 Apple Events)将“文档”发送到服务器,但服务器通过使用“打开”“文档”来处理请求(通过其他方式接收) NSDocumentController。您发现 NSDocumentController 需要 Info.plist 中的文档类型才能知道要使用哪个 NSDocument 子类。

这是您可以在 NSDocumentController 子类中覆盖的内容:

  • -typeForContentsOfURL:error::给定一个URL,return一个字符串,表示URL指的是什么类型的文档。默认行为通过查找在其标记中具有 URL 的 pathExtension 的类型来实现。根据你想要的严格程度,你可以做类似的检查,如果它是由你的客户发送的文档,你可以只 return 一个类型名称,或者你可以很懒惰,只是 return 一个常量字符串(如果您的服务器只处理一种类型的“文档”)。
  • -documentClassForType::给定文档类型名称,return NSDocument 子类实例化以管理该类型的文档。只要你只处理一种类型,因此只有一个 NSDocument 子类,你就可以无条件地 return 你的 NSDocument 子类;如果您有多个这样的子类,或者想为未来做计划,请将类型名称与每个已知类型名称和 return 适当的子类进行比较。
  • -displayNameForType::给定文档类型名称,return 包含该类型的用户可表示名称的本地化字符串(例如,英文中的“网页”,而不是“public.html”)。您的情况可能不需要。
  • -documentClassNames: Returns NSDocument 子类的名称数组,这些子类可能被实例化以管理应用程序可以处理的文档。对于您的目的可能不是必需的,但默认实现会参考您的 Info.plist.
  • -defaultType:用于新文档的文档类型的名称(如在 newDocument: 操作中)。如果您不从服务器创建新文档,则可能不相关,但默认实现会咨询您的 Info.plist(它 return 是您声明自己为编辑器的第一种类型)。

在您的 NSDocumentController 子类中覆盖这些方法后,您的文档控制器将不再参考 Info.plist,并且您可以删除 Info.plist 您不想让 LS 知道的文档类型信息关于。

如果您还没有使用 NSDocumentController 子类,则需要创建一个,并在程序的早期调用 [MyDocumentController sharedDocumentController](您甚至可能必须在 main 中调用在 nibs 加载之前执行它;自从我查看文档控制器实例化的时间和位置以来已经有一段时间了)。将该消息发送到您的子类将确保从该子类创建文档控制器,从而具有您在该子类中实现的行为。