提问者:小点点

从启动服务中隐藏 NSDocument 子类


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

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

Apple的核心Foundation Keys文档似乎指出了实现这一点的方法是使用LSHandlerRank属性。

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

对于客户端应用程序,我已将其设置为“所有者”。

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

对于服务器应用程序,我将其设置为“无”。

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

不幸的是,这没有效果。我仍然可以将文档放在服务器应用程序图标上,或者在客户端应用程序未运行时双击文档以将服务器应用程序向前移动。

另一个我认为很有前途的属性是CFBundleTypeRole。客户端应用程序的plist将其设置为“编辑器”。

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

此属性的文档非常稀疏,但它确实说“无”是一个选项。所以我在服务器应用程序列表中尝试了一下,然后我无法再以编程方式打开我的 NSDocument 文件。另一方面,将文档放在服务器应用程序图标上仍然会导致图标亮起,因此启动服务显然仍然认为服务器应用程序可以处理这种类型的文件。

总之,我需要的是对plist进行一些更改,以便我仍然可以通过编程来处理与我的NSDocument子类相关联的文件,但我不想让Launch Services知道我的(服务器)应用程序可以处理这些文档。这可能吗?

-跟进彼得·何西的回答-

谢谢你的回答。我不知道lsregistershell命令,它看起来很方便。转储在我的机器上产生46兆字节的数据!

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

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

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

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

是否有一些方法可以在没有URL的情况下打开文档,这样扩展名是什么或plist的设置方式都无关紧要?我在NSDocumentController类中没有看到任何这样的文档化方法。在我看来,测试似乎证实了NSDocument类依赖plist将文件扩展名链接到NSDocument子类。

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


共2个答案

匿名用户

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

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

    < Li > < code >-typeForContentsOfURL:error::给定一个URL,返回一个指示该URL引用的文档类型的字符串。默认行为通过查找标记中包含URL的< code>pathExtension的类型来实现这一点。根据您希望达到的严格程度,您可以进行类似的检查,并且只返回一个类型名称,如果它是由您的客户端合理发送的文档,或者您可以偷懒,只返回一个常量字符串(如果您的服务器只处理一种类型的“文档”)。 < Li > < code >-documentClassForType::给定一个文档类型名称,返回一个NSDocument子类来实例化管理该类型的文档。只要您只处理一种类型,因此只有一个NSDocument子类,您就可以无条件地返回您的NSDocument子类;如果您有多个这样的子类,或者希望为将来做好准备,请将类型名与每个已知的类型名进行比较,并返回适当的子类。 < Li > < code >-displayNameForType::给定文档类型名称,返回包含该类型的用户可显示名称的本地化字符串(例如,英文的“web page”,而不是“public.html”)。你的情况可能不需要。 < Li > < code >-document class names :返回NSDocument子类的名称数组,这些子类可能被实例化以管理应用程序可以处理的文档。对于您的目的来说可能不是必需的,但默认实现会参考您的info . plist。 < li>-defaultType:用于新文档的文档类型的名称(如< code>newDocument:操作中所示)。如果您不从服务器创建新文档,这可能不相关,但默认实现会查询您的Info.plist(它会返回您声明为编辑器的第一个类型)。

在NSDocumentController子类中重写这些方法后,您的文档控制器将不再查阅Info. plist,并且您可以删除有关您不希望LS了解的文档类型的Info.plist信息。

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

匿名用户

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

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

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

完成这两个操作的方法是使用 lsregister -dump。lsregister位于/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister,它的转储将为您提供有关注册了哪些类型(UTI),注册了哪些应用程序包(包括不同位置具有不同功能的不同版本),服务等的大量信息。

lsRegister-help将告诉您它的其他选项,其中一些可能有助于从LS的数据库中清除过时/无用的记录。

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

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

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