将基本的Cocoa应用程序转换为文档型应用程序

13

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

然而,这个应用程序确实应该是基于文档的,所以我开始试着将其转换。但是其中有些地方似乎并不起作用。例如,文件 -> 打开 菜单项始终被禁用(尽管我最终使 文件 -> 保存 菜单项可用了;最初它不可用)。此外,我可以点击红色的X按钮关闭窗口,虽然 文件 -> 关闭 菜单项本身被禁用了;但是,当我通过X按钮关闭窗口时,我的NSDocument实现(SPDocumentInfo)中的dealloc方法并没有被调用。我创建了一个样本全新的基于文档的应用程序进行比较;当我在那里关闭一个窗口时,SPDocument实现中的dealloc方法确实被调用了(正如我所期望的)。因此,这让我感到担忧。

我在项目中做了很多更改,包括:

  • 在.h文件中使SPDocumentInfo扩展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)是窗口的委托。

所以,我想知道在转换为文档型应用程序方面是否还有其他需要注意的事项?或者,是否有任何关于如何进行此操作的指南?(我已经搜索过了但找不到)。还是我应该从头开始制作一个新的文档型应用程序,并尝试将所有我们的内容都适应它?总的来说,有没有人有这方面的经验?


快速更新,如果有人在关注的话。我意识到早些时候,我们已经对连接一些菜单项到我们创建的特定插座进行了一些混淆,而不是让它们保持指向它们默认的第一个响应者插座。一旦我恢复了默认连接,菜单项就变为启用状态了。至于dealloc方法没有按预期调用的问题,我怀疑我们存在循环依赖的问题,其中类包含对彼此的引用,因此永远不会达到零的保留计数(这可能是另一个话题)。因此,我们可能能够解决这个问题。 - Dave Taubler
另一个更新:我不认为问题是循环依赖的问题。我在我的SPDocumentInfo类和示例应用程序中的MyDocument类中覆盖了释放方法。当我在示例应用程序中关闭窗口时,[NSDocumentController removeObject]和[NSWindowController _windowDidClose](以及一些自动释放池弹出)会调用释放。但是当我在我的应用程序中关闭窗口时,所有这些都不会发生在SPDocumentInfo上。因此,某种方式,文档控制器和/或窗口控制器在我的应用程序中没有按预期行事。 - Dave Taubler
4个回答

5

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

我采取了以下简单的步骤,它起作用了:

  • Generate a document based application
  • from this this generated project, copy the following section from the Info.plist (open the file with a normal text-editor):

    <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>
    

    and paste it in the Info.plist file in your own project.

  • Copy Document.swift from the generated document-based project into your own project.

  • it contains a method:

    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)
    }
    

    It creates a new window just the way you application normally would. If you storyboard has only one windowcontroller the 'withIDentifier'-field can contain something arbitrary. If you have more window controllers in your storyboard, the identifier needs to correspond to the right windowcontroller for new documents.


5
@Hans的回答中提供的所有内容都是正确的,但需要进行的最终更改非常微小,但增加了一定数量的功能,适用于基于文档的应用程序: Main.storyboard文件中,document元素有一个额外的属性需要删除:initialViewController="XXX-XX-XXX"
这可能是第二行上的最后一件事。删除它,Save…菜单选项以及其他一些菜单选项将默认启用,并且应用程序将在启动时正确识别文档对象。

这对我有用。最初,我从故事板视图中删除了“初始控制器”,这也将其从故事板的源视图中删除。直到我在故事板视图中添加它并从源视图中删除它才起作用。 - Morten J
这对我也起作用。我在界面构建器中点击指向窗口的箭头(在对象列表中称为“Storyboard入口点”),然后将其删除。之后,应用程序现在在启动时调用我的文档类。 - Dave Ceddia
如果不起作用,请按 CMD+k 清理构建文件夹。 - luin

4
好的,这次我确实有一个解决方案要呈现。
事实证明,我在SPDocumentInfo中有一个“window”实例变量(你会发现它指向与文档相关联的NSWindow)。这似乎引起了一系列事件(或更可能是阻止了一系列事件),导致SPDocumentInfo的dealloc没有在应该调用时被调用。当我将我的项目与示例基于doc的项目进行比较时,我没有注意到这一点,因为显然SPDocument还有一个名为“window”的成员变量,它也连接到相关的NSWindow。我在示例项目中看到了这个连接,它看起来与我的项目的连接完全相同,所以我没多想。
换句话说,我的问题部分原因是我碰巧决定连接到一个在NSDocument实现中的“window”outlet,并没有意识到我实际上正在遮蔽超类变量(我猜测它不像我的那样配置为“retain”,而是“assign”)。
因此,目前情况似乎还好,我认为我可以声明从非基于doc的应用程序转换为基于doc的应用程序确实是可能的(尽管存在令人讨厌的问题,但通常无痛)。

3

这更像是一种观点而不是直接的答案,但如果您是Mac新手并且对基于文档的应用程序不熟悉,那么最简单的方法肯定是从模板中创建一个新的基于文档的Xcode项目,并将相关代码移动到模板所需的位置。


Joshua,感谢您的回复。是的,你可能是对的。我可能会开始一个单独的基于文档的项目,看看将东西移植过来有多容易(代码方面我不太担心。我真正关心的是我们在NIB文件中完成的所有工作,包括所有复杂的连线)。 部分原因是,我感觉我已经接近成功了,也许只缺少一件事情。另一方面,这似乎是一个理解文档型应用程序“底层”发生的事情的好练习。 - Dave Taubler

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