Sample CodemacOSReviewed 2026-07-21View on Apple Developer

Developing a Document-Based App

At a glance

Item Summary
Purpose Read, edit, autosave, asynchronously write, and print plain-text documents.
Architecture AppKit document architecture: NSDocument bridges a small content model to storyboard-created window and view controllers.
State unit Every open document has its own Document, Content, and window-controller graph.

Project structure

DocumentBasedApp/
├── AppDelegate.swift
├── Document.swift
├── Content.swift
├── ViewController.swift
└── WindowController.swift

The compact folder is role-oriented rather than layer-nested. AppKit and the storyboard supply most lifecycle and composition behavior.

Overall architecture

Reference code

DocumentBasedApp/Document.swift:40 — the document creates its window graph and injects represented content.

    override func makeWindowControllers() {
        // ...
    }

Ownership and state

Ownership evidence

DocumentBasedApp/Document.swift:10 — each document retains its model and connected content controller.

class Document: NSDocument {
    // ...
}

AppKit’s document controller owns open-document lifecycle. A Document exclusively owns its Content value object and registers its windows through addWindowController. The view observes or edits the represented content through Cocoa bindings and delegate callbacks.

Class and protocol design

Type Responsibility Framework contract
Document Window creation, serialization, autosave capability, and printing NSDocument
Content Mutable string plus Data conversion NSObject; KVC-compatible dynamic property
ViewController Propagate represented objects and bracket text editing NSViewController, NSTextViewDelegate
WindowController Configure document-window behavior NSWindowController, NSWindowDelegate
AppDelegate Process-level termination policy NSApplicationDelegate

There is no app-defined protocol because AppKit’s document, view, and delegate contracts already define the seams.

Access control

Boundary Effect and rationale
App types are implicit internal The executable target can collaborate internally; nothing is exported as a reusable framework.
Content initializer is public The member is explicitly wide, but the enclosing internal class caps practical visibility to the app module.
Content storage is mutable and target-visible Cocoa bindings and the document both need access to it.
weak document computed property Avoids retaining the document from its UI graph.

DocumentBasedApp/Content.swift:11 shows the internal enclosing type that limits the otherwise public initializer at line 14.

Logic ownership and placement

Logic Owner Reason
File read/write and capability flags Document Required NSDocument boundary.
String-to-data conversion Content Serialization of the model itself.
Storyboard composition Document.makeWindowControllers One UI graph per document.
Begin/end editing notifications ViewController Originates from NSTextViewDelegate.
Print-view construction Document Uses document content and print lifecycle.

Design patterns

Pattern Evidence Purpose
Document architecture DocumentBasedApp/Document.swift:10 Makes open files the primary lifecycle and ownership unit.
MVC DocumentBasedApp/Content.swift:11, DocumentBasedApp/ViewController.swift:10 Separates serializable content from AppKit presentation.
Framework template method DocumentBasedApp/Document.swift:59 AppKit calls overridden read/write hooks.
Delegate DocumentBasedApp/ViewController.swift:40 Brackets editing for document change tracking.

Naming conventions

  • Core types use canonical AppKit role names: Document, ViewController, and WindowController.
  • Capability overrides read as AppKit vocabulary: autosavesInPlace, canAsynchronouslyWrite, and makeWindowControllers.
  • Content names the model rather than tying it to a text-view implementation.

Architecture takeaways

  • Let NSDocument be the per-file owner and composition root.
  • Keep serialization in the document/model boundary and UI concerns in controllers.
  • Use AppKit’s editing lifecycle instead of inventing a parallel dirty-state mechanism.
  • Explicitly wide members do not form a public API when their enclosing type is internal.

Source map

Source Role
DocumentBasedApp/Document.swift:10 Document lifecycle and composition
DocumentBasedApp/Content.swift:11 Text model and serialization helper
DocumentBasedApp/ViewController.swift:10 View/delegate integration
DocumentBasedApp/WindowController.swift:10 Window behavior