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
flowchart LR
AppKit["NSDocumentController / AppKit"] --> Document
Document --> Content
Document --> Storyboard["Main storyboard"]
Storyboard --> WindowController
WindowController --> ViewController
ViewController --> TextView["NSTextView"]
Document --> File["plain-text Data"]
Reference code
DocumentBasedApp/Document.swift:40 — the document creates its window graph and injects represented content.
override func makeWindowControllers() {
// ...
}Ownership and state
classDiagram
NSDocumentController o-- Document : lifecycle
Document *-- Content : creates
Document o-- NSWindowController : registers
NSWindowController *-- ViewController : storyboard
ViewController --> Content : representedObject
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, andWindowController. - Capability overrides read as AppKit vocabulary:
autosavesInPlace,canAsynchronouslyWrite, andmakeWindowControllers. Contentnames the model rather than tying it to a text-view implementation.
Architecture takeaways
- Let
NSDocumentbe 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 |