Hints and tips for working with the GNUstep developer tools and frameworks.
Codesigning macOS apps with GNUstep-make
GNUstep-make adds a stamp file (for example, stamp.make) inside the .app bundle directory, for internal dependency tracking. The macOS codesign tool rejects bundles containing unexpected files, so the stamp file must be removed before you sign your app.
Use a GNUstep-make hook to remove the stamp file before signing, for example, in a before-install:: hook.
GNUstep make and NEEDS_GUI
By default, GNUstep make encodes assumptions about whether it should link your target to the GUI framework (libs-gui on most platforms, AppKit on macOS). The default is yes for apps, and no for frameworks, libraries, and tools.
To change this, modify your GNUmakefile to set one of these variables to either yes or no:
NEEDS_GUI: applies to all targets in theGNUmakefile.[product_name]_NEEDS_GUI: applies to the specific product. For example, if your tool is calledMyTool, and it needs to link to the GUI framework, setMyTool_NEEDS_GUI = yes.
GNUstep-make hooks
GNUstep-make provides various double-colon rules that define integration points where you can run your own processes before, during, and after GNUstep-make actions.
For example, you can run additional steps after you build your app by defining after_$(GNUSTEP_INSTANCE)_all:: (where $(GNUSTEP_INSTANCE) is the name of your app), or supply the commands to run your tests when someone runs make check by defining internal-check::.
Global Hooks
These hooks are defined in Master/rules.make and apply to the entire build process:
IMP Caching
IMP caching is a common optimisation strategy in the hot loop of Objective-C code that uses older Objective-C runtime libraries. Instead of using the [object message:argument] syntax that invokes objc_msgSend() or objc_msgLookup(), the process splits message-sending into two phases:
- Use [NSObject methodForSelector:] to retrieve a pointer to the function that implements the method. The return type of this method is
IMP. - Call the function via the pointer found in the step 1, with the receiver as the first argument and the method selector as the second argument.
The goal of IMP caching is to perform the first step once on process launch, instead of using the Objective-C runtime to locate the function dynamically every time the process sends the message. In the Apple Objective-C runtime, IMP caching is less relevant because the runtime library maintains its own implementation cache (and invalidates it correctly when the process swizzles methods on classes, or loads categories that override existing methods).
Menu Structure
The menu structure for GNUstep apps is different when they’re running on macOS than on other platforms. macOS has a special “app menu” named after the app that contains the About, Settings, Services, and Quit items, among others.
On other platforms, the GNUstep menu structure follows the NeXTstep/OpenStep convention, in which the first menu is “Info”, which contains the “Info Panel…”, “Preferences”, and “Help” items. The Services menu is at the top level of the app’s menu bar, as are the hide and quit menu items. Use this structure on non-macOS desktop platforms, regardless of whether the active theme draws a vertical, NeXT-style menu, or a horizontal, Apple- or Windows-style menu bar.
Missing Categories
On some platforms, the dynamic loader doesn’t include a shared object in a process if it doesn’t detect that the executable uses any symbols from that object. This can cause a problem for pure Objective-C shared objects, for example frameworks that add categories to existing classes, because the loader doesn’t detect the method selectors as referenced symbols.
The symptom of this problem is a runtime exception that occurs when an object receives -[NSObject doesNotRespondToSelector:].
Renaissance Forms
An NSForm is a matrix of NSFormCell objects that associate labels with text fields.
To create one in Renaissance, use the <form/> element, where each child is a <formItem>, as in this example:
<form>
<formItem id="idField" title="User ID:"/>
<formItem id="emailField" title="Email Address:"/>
</form>
You can’t nest arbitrary views, for example NSTextField, inside a Renaissance form. Consider whether you need to use NSForm at all, or whether you can lay out text fields in the view directly instead.
Renaissance Lifecycle
Load markup programmatically during controller initialization, by calling -[NSBundle loadGSMarkupNamed:owner:]. The file’s #NSOwner reference is set to the object you pass as the owner. Call this method at the following points in your app’s lifecycle:
- To set the main menu, load markup that defines the menu after you initialize the app delegate, but before you call
NSApplicationMain. - To create UI that’s owned by the app delegate, load markup in your app delegate’s
-applicationDidFinishLaunching:method. - To create UI that’s owned by a controller, for example an
NSWindowController, load markup in the controller’s-initmethod. Don’t call methods that load other types of view files, for example,initWithWindowNibName:.
When Renaissance initializes and configures the view based on the contents of the markup file, it sends every object the -awakeFromGSMarkup message. Override this message to perform any actions after the view is configured and before someone interacts with it. Objects might receive other methods, like -windowDidLoad or -viewDidLoad, before Renaissance configures all of their connections.
Renaissance Outlets and Actions
Use XML elements and attributes in your .gsmarkup file to wire up outlets and actions between objects in the markup, and to the file’s owner.
Object references
Set the id attribute on an object element to give that object a unique identifier that you use when you reference it in outlets and actions. For example, if you define an NSImageView as <image id="profileImageView" editable="YES"/>, then you refer to it in an outlet or action as #profileImageView.
Set GNUSTEP_MAKEFILES
GNUstep-make resolves the files it includes relative to the GNUSTEP_MAKEFILES environment variable. Often, the value of this is /Library/GNUstep/Makefiles, but you can set it correctly by running this command:
% export GNUSTEP_MAKEFILES=`gnustep-config --var=GNUSTEP_MAKEFILES`
If you get build errors about missing /common.make, it’s because you didn’t set this variable.
The other way to set it is to source the GNUstep.sh script (or GNUstep.csh on C-style shells like tcsh), but as this script is inside the GNUSTEP_MAKEFILES folder this isn’t a good way to discover the location. You source the script in a .rc or .profile file.
Split View Orientation
In AppKit, NSSplitView’s -isVertical/-setVertical: property refers to the orientation of the divider, not the axis on which the views are ordered. In other words, a split view with isVertical equal to YES has a leading view (on the left in locales that read left-to-right), and vertical dividers splitting subviews that are organized horizontally. A split view with isVertical equal to NO has a top view, and horizontal dividers splitting subviews that are organized vertically.
Split View Sizing
An NSSplitView might assign a zero size to a subview that doesn’t request an explicit size. Provide an explicit initial size, and use autoresizing masks or autolayout constraints that allow the subview to occupy the space provided by the split view.
In Renaissance, use the hexpand attribute for horizontal stretching, and the vexpand attribute for vertical stretching.
