【问题标题】:Converting a basic Cocoa application to a document-based application将基本 Cocoa 应用程序转换为基于文档的应用程序
【发布时间】:2011-02-23 22:03:02
【问题描述】:

我和我的团队一直在开发一个现有的、非基于文档的 Cocoa 应用程序。这是我们的第一个 Cocoa 应用程序,尽管到目前为止我们已经完成了许多 iOS 应用程序。

不过,该应用确实应该是基于文档的,所以我开始尝试对其进行转换。但是这里和那里的事情似乎没有奏效。例如,文件 -> 打开菜单项被永久禁用(尽管我最终启用了文件 -> 保存菜单项;最初它不会启用)。另外,我可以点击红色的 X 来关闭一个窗口,虽然 File -> Close 菜单项本身是禁用的;但是,当我通过 X 按钮关闭窗口时,不会调用我的 NSDocument 实现 (SPDocumentInfo) 中的 dealloc 方法。我创建了一个示例、全新的基于文档的应用程序,仅用于比较;当我在那里关闭一个窗口时,确实调用了 SPDocument 实现的 dealloc 方法(正如我所期望的那样。)所以这让我很担心。

我在这里和那里对项目进行了很多更改;它们包括:

  • 使 SPDocumentInfo 在 .h 文件中像这样扩展 SPDocument:

    @interface SPDocumentInfo : NSDocument <NSWindowDelegate>
    
  • 在 SPDocumentInfo 中实现了以下内容:

    - (NSString *)windowNibName {
        return @"SPDocument";
    }
    
    - (void)windowControllerDidLoadNib:(NSWindowController *) aController {
        [super windowControllerDidLoadNib:aController];
    }
    
    - (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError {
        NSString *xml = [self toXml];
        return [xml dataUsingEncoding:NSUTF8StringEncoding];
    }
    
    - (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError {
        // will make this work later
        if ( outError != NULL ) {
            *outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:unimpErr userInfo:NULL];
        }
        return YES;
    }
    
  • 编辑 .plist 文件以添加“文档类型”。除其他外,定义了“Cocoa NSDocument Class”=“SPDocumentInfo”。

  • 更改了 SPDocumentInfo 中的一些连接以匹配基于文档的示例应用程序中的连接。例如,在 SPDocument.nib 中,文件的所有者(代表 SPDocumentInfo)是窗口的委托。

所以,我想知道在转换为基于文档的应用程序时是否还缺少其他类型的东西。或者,有没有关于如何做到这一点的指南? (我看过但找不到)。或者我应该从一个新的基于文档的应用程序重新开始,然后尝试将我们所有的东西都改造成它?一般来说,有没有人有这方面的经验?

【问题讨论】:

  • 快速更新,以防有人关注。我之前意识到,我们在将一些菜单项连接到我们创建的特定网点时有些麻烦,而不是让它们保持指向默认的 First Responder 网点。一旦我恢复了默认连接,菜单项就会启用。至于 dealloc 方法没有按预期调用,我怀疑我们有一个循环依赖的问题,其中类包含对彼此的引用,因此永远不会达到零的保留计数(我猜,这是另一个话题)。所以我们也许可以解决
  • 另一个更新:我不认为这个问题是循环依赖之一。我覆盖了我的 SPDocumentInfo 类和示例应用程序中的 MyDocument 类中的发布方法。当我在示例应用程序中关闭一个窗口时,释放由 [NSDocumentController removeObject] 和 [NSWindowController _windowDidClose] 调用(以及一些自动释放池弹出)。但是当我在我的应用程序中关闭窗口时,SPDocumentInfo 会发生 none 。所以不知何故,文档控制器和/或窗口控制器在我的应用程序中没有按预期运行。

标签: cocoa


【解决方案1】:

@Hans 的回答提供的所有内容都是正确的,但最终所需的更改完全是微不足道的,但添加了基于文档的应用程序附带的大量功能:

Main.storyboard 文件中,document 元素有一个额外的属性需要删除:initialViewController="XXX-XX-XXX"

这可能是第二行的最后一件事。删除它,Save… 菜单选项以及其他一些菜单选项将默认正确启用,并且应用程序将在启动时正确识别文档对象。

【讨论】:

  • 这是为我做的。最初,我从情节提要视图中删除了“初始控制器”,这也将其从情节提要的源视图中删除。直到我将它重新添加到情节提要视图中并从源视图中删除它才起作用。
  • 这对我也有用。我在界面生成器中通过单击指向窗口的箭头(在对象列表中称为“故事板入口点”)并删除它来完成此操作。之后,应用程序现在在启动时调用我的 Document 类。
  • 如果编译文件夹不起作用,请尝试按 CMD+k 清理构建文件夹。
【解决方案2】:

一个常见的建议是创建一个新的基于文档的应用程序并将所有现有代码移到其中。这对于一个配置了各种东西的大型工作区来说可能很麻烦。更不用说破坏版本控制了。

我采取了以下简单的步骤,并且成功了:

  • 生成基于文档的应用程序
  • 从这个生成的项目中,从 Info.plist 中复制以下部分(使用普通文本编辑器打开文件):

    <key>CFBundleDocumentTypes</key>
    <array>
        <dict>
            <key>CFBundleTypeExtensions</key>
            <array>
                <string>mydoc</string>
            </array>
            <key>CFBundleTypeIconFile</key>
            <string></string>
            <key>CFBundleTypeName</key>
            <string>DocumentType</string>
            <key>CFBundleTypeOSTypes</key>
            <array>
                <string>????</string>
            </array>
            <key>CFBundleTypeRole</key>
            <string>Editor</string>
            <key>NSDocumentClass</key>
            <string>$(PRODUCT_MODULE_NAME).Document</string>
        </dict>
    </array>
    

    并将其粘贴到您自己项目的 Info.plist 文件中。

  • 将生成的基于文档的项目中的 Document.swift 复制到您自己的项目中。

  • 它包含一个方法:

    override func makeWindowControllers() {
        // Returns the Storyboard that contains your Document window.
        let storyboard = NSStoryboard(name: "Main", bundle: nil)
        let windowController = storyboard.instantiateController(withIdentifier: "Document Window Controller") as! NSWindowController
        self.addWindowController(windowController)
    }
    

    它会像您通常的应用程序一样创建一个新窗口。如果您的故事板只有一个窗口控制器,则“withIDentifier”字段可以包含任意内容。如果您的故事板中有更多窗口控制器,则标识符需要对应于新文档的正确窗口控制器。

【讨论】:

    【解决方案3】:

    好的,这一次我合法地确实有一个解决方案要介绍。

    原来我在 SPDocumentInfo 中有一个“window”实例变量(正如您猜想的那样,它指向与文档关联的 NSWindow)。这似乎导致了一连串事件(或者更有可能阻止了一连串事件),导致 SPDocumentInfo 的 dealloc 在应有的时候没有被调用。当我将我的项目与基于 doc 的示例项目进行比较时,我没有注意到这一点,因为显然 SPDocument also 有一个名为“window”的成员变量,它是 also 连接的到相关的 NSWindow。我在示例项目中看到了该连接,它看起来与我的项目的连接相同,所以我没有三思而后行。

    换句话说,我的部分问题是我只是巧合地决定连接一个“窗口”出口 NSDocument 实现,并没有意识到我实际上是在隐藏一个超类变量(我猜这是,不像我的,配置为“分配”而不是“保留”)。

    所以,目前看来一切都还好,我想我可以声明确实有可能(尽管我的问题很烦人,通常没有痛苦)从非基于文档的应用程序转换为基于文档的应用程序。

    【讨论】:

      【解决方案4】:

      这更多的是一种意见,而不是直接的答案,但如果您是 Mac 端和基于文档的应用程序的新手,那么阻力最小的路径肯定是从模板并将您的相关代码移过来,将其插入需要的模板位置。

      【讨论】:

      • 约书亚,感谢您的回复。是的,你可能是对的。我可能会开始一个单独的基于文档的项目,看看它是多么容易移植东西(代码,我不太关心。它真的是我们在 NIB 文件中完成的所有东西,包括所有复杂的接线,我担心)。部分地,我觉得我几乎在那里,也许只是缺少一件事。在某种程度上,这似乎是一个很好的练习,可以准确地理解基于文档的应用程序中“幕后”发生的事情。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-15
      • 1970-01-01
      • 1970-01-01
      • 2018-07-25
      • 1970-01-01
      相关资源
      最近更新 更多