Document based app with Gorm and PC

From GNUstepWiki
Revision as of 21:10, 7 September 2024 by Buzzdee (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

About

As a result of learning about document based applications, and how to implement them with PC and Gorm, I ended up with this, not overly useful, but working test application: https://github.com/buzzdeee/TestDocument

It features:

  • following MVC pattern
  • built with PC and Gorm
  • separate AppController and AppDelegate
  • support for multiple document types
    • separate .gorm files for each supported document type
  • KVC/KVO
  • ARC
  • using some modern syntax
  • Localization
    • and switched default language to something else than English

I'll cover some topics here, that will help understanding the application, and it's workflow.

MVC and document based apps

Cocoa MVC (Model-View-Controller) is a design pattern used in Apple's Cocoa framework to separate concerns in an application. The Model represents the data and business logic, the View displays the data and handles user interface elements, and the Controller manages communication between the Model and View, updating the UI based on changes in the data. This separation promotes modularity, making the code more maintainable and reusable.

In a Cocoa (or GNUstep) document-based app that supports multiple documents, the classes involved can be mapped to the Model-View-Controller (MVC) pattern. Here’s how the various components like NSDocument, NSDocumentController, NSWindowController, document model classes, and UI built with Gorm fit into MVC:

  1. Model (M) The model represents the application's data and business logic. In a document-based app, the following components are part of the model:
    1. Plain classes for the document models: These are your custom classes that contain the data and logic specific to your document. These classes are independent of the UI and are responsible for holding the document's data, performing any processing, and managing state.
    2. For example, if you’re building a text editor, the model class might store the text, keep track of formatting, and manage metadata (like the document title or file path).
    3. NSDocument: While NSDocument primarily fits into the controller part of MVC, it also has some aspects of the model in Cocoa’s architecture. NSDocument handles saving, loading, and managing the undo functionality, so it acts as a mediator between the model data and persistence (reading/writing the file). It interacts closely with the document model classes to manage the actual content.
  2. View (V) The view is responsible for displaying the data and handling user input.
    1. UI built with Gorm: Gorm is used to build the UI components in the app, which form the view. These include:
      1. Windows, menus, buttons, text fields, etc. created in Gorm are part of the view layer. They display the content of your document and allow users to interact with it.
      2. For example, if you're using a text editor, the NSTextView or custom views to display text are part of the view layer.
    2. NSWindowController: The NSWindowController is responsible for managing the app’s windows and handling the interaction between the view and the document (controller). It loads the interface from a .gorm file (which contains the window and its views) and binds the UI elements to the document data. In strict MVC, it is part of the controller, but because it manages UI and views, it has aspects of both the controller and view layers.
  3. Controller (C) The controller manages the interactions between the model and the view, mediating user input, updating the view, and manipulating the model as needed.
    1. NSDocument: As mentioned, NSDocument serves as the document controller in this architecture. It communicates with the model (plain document classes) to retrieve and save the document data and updates the view when necessary. It also handles document-specific tasks like managing undo/redo, saving, and opening documents. It doesn't directly manage the UI but works closely with the NSWindowController to coordinate the display of the document.
    2. NSDocumentController: This class is responsible for managing all documents in the application. It keeps track of which documents are open, handles creating new documents, and ensures that the correct document is presented in the appropriate window. NSDocumentController also manages interactions like opening recent files, handling document lifecycle events (new, open, close), and interacting with NSDocument objects. It plays a key role in coordinating the flow between the user’s actions and the document management.
    3. NSWindowController: Though it has a close relationship with the view, NSWindowController also has controller responsibilities. It coordinates with NSDocument to ensure that the correct data is presented in the view. It manages the window’s lifecycle and keeps the UI in sync with the document data.

Breakdown of the MVC components

  • Model: Plain document classes (contain the data and business logic).
  • View: UI components built with Gorm, which display the document’s data.
  • Controller:
    • NSDocument: Bridges the model and the view, managing document-specific tasks (loading, saving, undo/redo).
    • NSWindowController: Manages windows and interfaces with NSDocument to update the views.
    • NSDocumentController: Oversees multiple documents, handles document creation, and ensures that the correct documents are open/active.

Workflow example in MVC context

  1. User Action: A user opens an existing document from the File menu (View).
  2. Controller Response: The NSDocumentController coordinates opening the file by creating or loading the corresponding NSDocument object (Controller).
  3. Model Interaction: The NSDocument interacts with the plain document model class to load the data from the file (Model).
  4. UI Update: The NSWindowController ensures that the document’s data is reflected in the views loaded from the Gorm interface file (View).
  5. User Edits: The user modifies the document content via the UI (View), and the NSDocument updates the document model class accordingly (Model).

In this way, each class in a Cocoa (GNUstep) document-based app has a clear responsibility within the MVC framework, ensuring separation of concerns and maintainability of the application’s architecture.

Development Environment

The application was developed on OpenBSD, with just the main GNUstep packages installed. If you're up to it, and it's probably one of the easiest ways to get a a modern GNUstep development environment supporting libobjc2 and ARC up and running ;)

  1. Install OpenBSD on a spare machine or VM, enable xdm
  2. install GNUstep as easy as: pkg_add gnustep-desktop
  3. configure your .xsession alike:
if [ -f /usr/local/share/GNUstep/Makefiles/GNUstep.sh ];then
       . /usr/local/share/GNUstep/Makefiles/GNUstep.sh
fi
export GNUSTEP_STRING_ENCODING=NSUTF8StringEncoding
export LC_ALL='en_EN.UTF-8'
export LC_CTYPE='en_US.UTF-8'
if [ -x /usr/local/bin/gpbs ];then
       /usr/local/bin/gpbs
fi
if [ -x /usr/local/bin/gdnc ];then
       /usr/local/bin/gdnc
fi
wmaker &
if [ -x /usr/local/bin/GWorkspace ];then
       /usr/local/bin/make_services
       /usr/local/bin/GWorkspace
fi

With these simple steps, you'll have a working GNUstep development environment.

Debugging

If you want to debug, you may want to install egdb from packages as well. To ease debugging, it might make sense to have the gnustep library sources around, and those build with debugging symbols, to do so:

  1. add the following to your /etc/doas.conf file:
          permit nopass keepenv :wsrc
          permit nopass keepenv :wheel
    1. NOTE: this allows passwordless root for the main user
  1. echo "echo 'DEBUG=-g -O0'" > /etc/mk.conf
  2. echo "echo 'SUDO=doas'" >> /etc/mk.conf
  3. doas pkg_delete gnustep-base gnustep-libobjc2 gnustep-make
  4. download and extract the ports.tar.gz to /usr/ports
  5. cd /usr/ports/x11/gnustep/
  6. make install
  7. make patch

just reboot, or relogin, then you should have your GNUstep packages around, built with debugging symbols, as well as the sources on disk. This will dramatically ease debugging using egdb.

ProjectCenter and Gorm

GNUstep ProjectCenter and Gorm are tools for developing applications using the GNUstep framework.

  • ProjectCenter is an integrated development environment (IDE) that helps developers manage their projects, providing tools to create, organize, and compile GNUstep applications. It simplifies the setup and management of project files, source code, and build configurations, making it easier to develop Objective-C applications.
  • Gorm: (GNUstep Object Relationship Modeler) is a visual interface builder similar to Apple's Interface Builder. It allows developers to design user interfaces by dragging and dropping UI components, connecting them to the application's code, and managing the relationships between UI elements and objects, without writing the UI code manually.

Together, these tools streamline the development of GNUstep-based applications by providing a cohesive environment for both project management and user interface design.


About ARC

Automatic Reference Counting (ARC) in Cocoa is a memory management feature that automatically manages the retain and release of objects. ARC ensures that objects are deallocated when they are no longer needed by automatically inserting the necessary memory management calls (retain, release, autorelease) at compile time. This reduces the risk of memory leaks and eliminates the need for developers to manually manage memory, making code cleaner and less error-prone, while improving performance by avoiding manual reference counting.

use ARC in your ProjectCenter application

GNUstep PC doesn't default to use ARC when creating projects. Therefore you have to tweak the default Application a bit:

  • Open the Project Inspector -> Build Attributes
    • ObjC Compiler Flags: -fobjc-arc -g
  • find the AppController.m in the Classes and remove the [super dealloc]; line from the dealloc method
  • find the <YOUR APPLICATION>_main.m in the Other Sources and add @autoreleasepool to it, so it looks alike:
int 
main(int argc, const char *argv[])
{
  @autoreleasepool
    {
      return NSApplicationMain (argc, argv);
    }
}

With ARC, explicit calls to dealloc are prohibited, and the @autoreleasepool context takes care about the memory management within your application.

Separate AppDelegate and AppController responsibilities

GNUstep ProjectCenter default applications just provide a AppController class. To separate concerns, it might make sense to separate out some life-cycle management from the AppController into the AppDelegate.

In a document-based application, both AppDelegate and AppController play essential roles in the lifecycle and behavior of the app, but they serve different purposes. Let's clarify their jobs:

AppDelegate: The AppDelegate class is responsible for handling the high-level app lifecycle and state transitions. It is defined in the Application Kit as a delegate for NSApplication. This class manages events such as the launch, termination, and background/foreground state of the application. In a document-based app, the AppDelegate is not directly involved with managing individual documents (that's the job of NSDocument and NSDocumentController), but it coordinates the application-wide behavior.

We'll let the AppDelegate handle Application Lifecycle Events:

  • applicationDidFinishLaunching: This method is called after the application has launched, and you can use it to perform setup tasks, like initializing application-wide data or settings.
  • applicationWillTerminate: Called before the application terminates, and you can use it to save data or clean up resources.

AppController: The AppController class, though not a part of the default AppKit architecture, is a custom controller you can introduce. Its primary function is to act as the controller in the MVC pattern but on an application-wide level. This class often handles custom application logic that is not tied to a specific document or the app's lifecycle, unlike AppDelegate.

In a document-based app, AppController might:

  • Coordinate Document Management: It may interact with NSDocumentController to manage app-wide document-related features, like managing multiple types of documents or integrating additional functionality across all documents.
  • Setup Application-Specific Logic: It could handle custom actions like opening recent documents, setting up application-wide notifications, or dealing with specific app-wide user interface elements.
  • Interface with UI Components: It might be responsible for handling non-document-specific UI components like toolbars, menus, or status indicators that aren't directly tied to an individual document.

Custom Document Controller

Important facts about the custom NSDocumentController:

  • It should be a subclass of NSDocumentController
  • it's a Singleton, means, there's ever only one NSDocumentController class or subclass thereof in your application
  • You might want to load it early, so the first instantiated NSDocumentController, will be your custom subclas.
    • We're using the CustomInitializer class to help with that.