Difference between revisions of "Document based app with Gorm and PC"

From GNUstepWiki
Jump to navigation Jump to search
 
(One intermediate revision by the same user not shown)
Line 1: Line 1:
 
== About ==
 
== About ==
  
This tutorial is going to show off how to create a Document based app, it'll cover the following:
+
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
  
* simple overview about MVC and document based apps
+
It features:
** what classes to be used and how it fits into the MVC model
+
* following MVC pattern
* using ProjectCenter and Gorm
+
* built with PC and Gorm
** simple introduction into both tools, some tips 'n tricks
+
* separate AppController and AppDelegate
** how to enable ARC in ProjectCenter
+
* support for multiple document types
* KVC/KVO to keep the UI in sync with the model
+
** separate .gorm files for each supported document type
* the App we're going to create will be localized
+
* KVC/KVO
** we'll switch the default language to something else than English ;)
+
* ARC
* the app will handle multiple documents
+
* using some modern syntax
 +
* Localization
 +
** and switched default language to something else than English
  
This tutorial is not meant for long time Mac Cocoa Objective-C developers. It's more for people like me, lacking a Mac, but
+
I'll cover some topics here, that will help understanding the application, and it's workflow.
want to give the Cocoa Framework a try. To be honest, I read through "Cocoa Programming Developer's Handbook" by David Chisnall,
 
but still, afterward it took me a couple of weeks with the help of ChatGPT to get the test app we're going over here, to finally
 
work and being well structured. Reading the book, is very helpful in the first place, and dramatically helps talking to ChatGPT.
 
ChatGPT is actually not too bad, it's even aware of some differences between GNUstep and Cocoa, but sometimes hallucinates methods
 
that don't exist, or it might guide you into the wrong direction.
 
 
 
== Structure ==
 
 
 
* first there will be an overview about MVC, and the classes we're going to use in the App, and how these fit into the paradigm
 
** how things are wiring up
 
* then continue with a quick introduction into PC and Gorm
 
** some small tips 'n tricks to make using them a bit easier
 
* then we'll start implementing the application
 
** step by step, dealing with one type of document to be managed in the app
 
* at the end, you should be able to implement handling of a second type of document
 
 
 
Code for the example application can be found here: https://github.com/buzzdeee/DocumentBasedAppTutorial
 
  
 
== MVC and document based apps ==
 
== MVC and document based apps ==
Line 73: Line 59:
 
== Development Environment ==
 
== Development Environment ==
  
This tutorial 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 ;)
+
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 ;)
  
 
# Install OpenBSD on a spare machine or VM, enable xdm
 
# Install OpenBSD on a spare machine or VM, enable xdm
Line 125: Line 111:
  
 
Together, these tools streamline the development of GNUstep-based applications by providing a cohesive environment for both project management and user interface design.
 
Together, these tools streamline the development of GNUstep-based applications by providing a cohesive environment for both project management and user interface design.
 
== Creating your Project ==
 
With all that prepared, you're now ready to create the basic project:
 
 
=== ProjectCenter ===
 
With either just gnustep-desktop package(s) installed, or even built by yourself from ports, you should now be able to just
 
start ProjectCenter from the command line, i.e. xterm window.
 
 
Once it's started, you want to configure it a bit, to ensure a smooth experience ;)
 
 
* Open the '''Preferences''' Build menu
 
** ensure '''External Build Tool''' points to ''/usr/local/bin/gmake'' or wherever your GNU Make is when you're not on OpenBSD. A BSD make just won't work.
 
* Open the '''Preferences''' Miscellaneous menu
 
** ensure '''Debugger''' points to ''/usr/local/bin/egdb'' or whatever is the right one for your platform
 
** ensure '''Editor''' is ''ProjectCenter''. That should be the default, you may use others, i.e. Gemas...
 
  
  
Now Open the ''Project'' -> ''New'' menu, select a suitable place where you want to have your project reside.
+
== About ARC ==
Ensure '''Project Type''' is set to '''Application''' and give it a name. We'll name our project '''DocumentBasedApp''' as you can see in the screenshot below.
 
(You may actually not want to choose /tmp as your destination folder ;)
 
  
 +
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.
  
Voila, your first project.
+
=== use ARC in your ProjectCenter application ===
  
Now do some configurations, to add support for ARC, debugging, multiple languages...
+
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'' -> ''Project Languages''
 
** and add a second language, i.e. ''German''
 
* Open the ''Project Inspector'' -> ''Project Attributes''
 
** switch the ''Language'' to ''German''
 
** you may specify a bundle identifier, i.e. com.mydomain.documentbasedapp
 
** open the ''Document Types...'' button
 
*** activate the '''Document-based''' checkbox
 
*** enter the fields below (always hit return when finished editing):
 
**** ''Type'': MyDoc1
 
***** As it's identified throughout the application in code
 
**** ''Name'': My Doc 1
 
***** Not exactly sure where this is used
 
**** ''Icon'': not yet
 
***** we don't need it yet
 
**** ''Extension'': doc1
 
***** the file extension for your document type, without the leading .
 
**** ''Role'': Editor
 
***** might be ''Editor'' or ''Viewer'' depending on whether your app will edit or only view your documents
 
**** ''Class'': MyDoc1
 
***** the class in your code, that will handle these types of documents
 
*** finally hit the '''+''' sign to add it to the list of document types
 
*** you may now do the same for a second document type in a similar way: MyDoc2, or do it later
 
 
* Open the ''Project Inspector'' -> ''Build Attributes''
 
* Open the ''Project Inspector'' -> ''Build Attributes''
 
** ''ObjC Compiler Flags'': -fobjc-arc -g
 
** ''ObjC Compiler Flags'': -fobjc-arc -g
*** to tell it to use ARC and add debugging symbols when building
 
** ''Install Domain'': you may change it to ''User''
 
* You may Open the ''Project Inspector'' -> ''Project Description''
 
** update there as well as you see fit
 
 
With that, your project should be well configured.
 
 
=== ARC support finished ===
 
 
* find the ''AppController.m'' in the ''Classes'' and remove the ''[super dealloc];'' line from the dealloc method
 
* find the ''AppController.m'' in the ''Classes'' and remove the ''[super dealloc];'' line from the dealloc method
* find the ''DocumentBasedApp_main.m'' in the ''Other Sources'' and add @autoreleasepool to it, so it looks alike:
+
* find the ''<YOUR APPLICATION>_main.m'' in the ''Other Sources'' and add @autoreleasepool to it, so it looks alike:
  
 
  int  
 
  int  
Line 195: Line 136:
 
  }
 
  }
  
With ARC, explicit calls to dealloc are prohibited, and the ''@autoreleasepool'' takes care about the memory management within your application.
+
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.
  
=== Build the app ===
+
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.
  
* open the build window
+
'''AppController''':
** configure the ''Build Options''
+
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.
*** enable ''verbose output'''
 
** hit the build button
 
  
With a bit of luck and no typos, the build should have succeeded!
+
In a document-based app, AppController might:
  
=== Launch the app ===
+
* ''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.
  
* from the main project window, hit the launcher icon to open the launcher window
+
=== Custom Document Controller ===
** NSLog output will show up here when you add it for your debugging aid
 
* hit the launch icon
 
  
Voila, your first (quite useless) app is up and running.
+
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.

Latest revision as of 21:10, 7 September 2024

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.