Introduction to Objective-C in MorphOS 3.10

by Jacek Piszczek
Why Objective-C?

Objective-C is conceptually similar to BOOPSI - it's generally an add-on to the C programming language. In both Obj-C and BOOPSI calling a method implies calling a dispatcher function that resolves the actual method to call and invokes it. With the addition of reference counting to BOOPSI in MorphOS, both follow the same memory management principles.

The main difference comes from the fact that BOOPSI classes need to be manually created with functions being manually assigned their IDs and let's not even start on the extra hassle of having to write the code for the dispatchers. This made programmers reluctant to add new classes in their applications, in turn making the overall code less object oriented.

Here's where Objective-C fills in.

By Example: a simple MUI class in C
/* Each BOOPSI class needs a data structure... */ struct MyData { LONG dummy; }; /* Implementation in BOOPSI */ ULONG mAskMinMax(struct IClass *cl,Object *obj,struct MUIP_AskMinMax *msg) { DoSuperMethodA(cl,obj,msg); msg->MinMaxInfo->MinWidth += 100; msg->MinMaxInfo->DefWidth += 120; msg->MinMaxInfo->MaxWidth += 500; msg->MinMaxInfo->MinHeight += 40; msg->MinMaxInfo->DefHeight += 90; msg->MinMaxInfo->MaxHeight += 300; return(0); } ULONG mDraw(struct IClass *cl,Object *obj,struct MUIP_Draw *msg) { int i; DoSuperMethodA(cl,obj,msg); SetAPen(_rp(obj),_dri(obj)->dri_Pens[TEXTPEN]); for (i=_mleft(obj);i<=_mright(obj);i+=5) { Move(_rp(obj),_mleft(obj),_mbottom(obj)); Draw(_rp(obj),i,_mtop(obj)); Move(_rp(obj),_mright(obj),_mbottom(obj)); Draw(_rp(obj),i,_mtop(obj)); } return(0); } /* Then comes the dispatcher and class initialization */ DISPATCHER(MyClass) { switch (msg->MethodID) { case MUIM_AskMinMax: return(mAskMinMax(cl,obj,(APTR)msg)); case MUIM_Draw : return(mDraw (cl,obj,(APTR)msg)); } return(DoSuperMethodA(cl,obj,msg)); } DISPATCHER_END ... if (!(mcc = MUI_CreateCustomClass(NULL,MUIC_Area,NULL,sizeof(struct MyData), DISPATCHER_REF(MyClass)))) ... MUI_DeleteCustomClass(mcc);
For comparison: a simple MUI class in Objective-C
/* Each Objective-C class needs to be declared - this is a declaration of ** class MyArea that inherits from MUIArea (MUIC_Area) */ @interface MyArea : MUIArea @end /* Implementation in Objective-C */ @implementation MyArea - (BOOL)draw:(ULONG)flags { struct RastPort *rp = self.rastPort; [super draw:flags]; SetAPen(rp, self.drawInfo->dri_Pens[TEXTPEN]); for (int i= self.left ; i<= self.right; i+= 5) { Move(rp, self.left, self.bottom); Draw(rp, i, self.top); Move(rp, self.right, self.bottom); Draw(rp, i, self.top); } return YES; } - (void)askMinMax:(struct MUI_MinMax *)minmax { minmax->MinWidth += 100; minmax->MinHeight += 40; minmax->DefWidth += 120; minmax->DefHeight += 90; minmax->MaxWidth += 500; minmax->MaxHeight += 300; } @end
But an Objective-C class still needs a BOOPSI dispatcher, right?
Yes! As the Objective-C classes get initialized and later instantiated, the mui.framework will create the BOOPSI counterparts dynamically. One of the strengths of Obj-C is that it is possible to determine which methods were overloaded by the programmer at runtime.
What part of Obj-C runtimes and frameworks are available under MorphOS?
The Objective-C support was generally built around the GCC 5 compiler, which implies partial support for the 2.0 variant (meaning no ARC (yet!)). Currently there are two frameworks being shipped with MorphOS: the ob.framework and mui.framework.
The ob.framework

The framework is essentially what Foundation is in Cocoa. It contains the root Object class as well as core application, threading, signalling, messaging and data management classes. All of the data classes are (apart from their prefixes) compatible with Foundation's classes. The classes are:

  • OBArray
  • OBData
  • OBDictionary
  • OBEnumerator
  • OBHashTable
  • OBIndexSet
  • OBMapTable
  • OBNumber
  • OBSet
  • OBString

and more including mutable variants, object coding support, autorelease pools, etc.

The MorphOS native parts include:

  • OBApplication - base class for the Objective-C applications under MorphOS
  • OBContext - a global runtime info class
  • OBLocalization - in-source localization support (no writing .cd files by hand!)
  • OBPerform - class supporting all sorts of method invocation
  • OBPerformQueue - a generic replacement for MUI's PushMethod
  • OBSignalHandler - exec.library signal handler class
  • OBStringNative - convenience class handling the system charset encoded strings
  • OBSystemTime - convenience class to query the system time
  • OBThread - threading support class
  • OBTimer, OBScheduledTimer - timing support classes
The mui.framework

The framework is essentially a wrapper for the MUI's BOOPSI interface. Each of the MUI's public classes and many 3rd party classes get their equivalents the Obj-C framework. The underlying BOOPSI instances are created on demand, usually when the MUI application and its windows are constructed.

mui.framework's feature list
  • All MUI methods can be overloaded

    So mAskMinMax(IClass *cl, Object *obj, struct MUIP_AskMinMax *msg) becomes
    - (void)askMinMax:(struct MUI_MinMax *)minmaxinfo
  • Setters and getters for MUI attributes can be overloaded

    So MUIA_Disabled becomes
    - (BOOL)disabled;
     - (void)setDisabled:(BOOL)disabled;
    And can be used like myObject.disabled = YES; (Objective-C 2.0)
  • Setting and getting MUI attributes works even if the underlying BOOPSI object has not yet been instantiated!
  • No more murky ownership issues - if you add an object to a MUIFamily subclass it's retained. If you set a value to a property that has an object type - it's retained.

    Notifies are a notable exceptions here, since the notify target cannot be retained, since that'd often cause a retain cycle.
  • Unicode - everywhere! Since ob.framework's OBString class is Unicode aware, all strings in MUI apps should be Unicode as well. The framework will convert them to the native system codepage if necessary when passing to the mui.library.

    Internationalization of MUI apps should be done using OBLocalization:
    button.contents = OBL(@"Click Me", @"Label of a button that the user needs to click");
    where the first string will be displayed as the text of the button and the latter is to be a hint to the translator about the purpose of the label.

    The .cd file will be generated automatically with the obcd utility.

    To initialize localization support just call
    [OBLocalizedString openCatalog:@"myapplication.catalog" withLocale:NULL];
    right after creating the MUIApplication object.
  • Notifications on MUI attributes can be set up as usual:
    [addRowButton notify:@selector(selected) trigger:FALSE
    performSelector:@selector(addRow) withTarget:_list];
    ... or you can overload the MUIButton class and its - (void)selected method!
  • The Notify on Dead Object issue can now easily be worked around thanks to the autorelease pools - simply add [[self retain] autorelease] in the method the notify invokes.

    There's also a simple detection of zombie objects - where calling a method on an already dealloc'd object will produce an informative debug message rather than a random crash.
  • The MUIFamily protocol has been extended to several classes which followed similar principles, but did not inherit from MUIC_Family directly. The protocol is based on the OBArray class' interface. The classes that implement the protocol are MUIApplication, MUIGroup, MUIMenu, MUIList, MUIMenuitem, MUIMenustrip.
  • MUI's obsolete input handling methods have been removed, classes need to overload the handleEvent:muikey: method and use the handledEvents property to set handled IDCMP flags.
  • MUIRequest, MUICheckmark, MUIButton are now classes to avoid having to call MUIMakeObject().
  • The underlying BOOPSI instance may always be accessed via the muiInstance property of the MUIObject class.
  • Where applicable, method parameters and properties will use strong typing - that is, MUIArea's contextMenu property will return MUIMenustrip * rather than MUIObject * or id.
  • MUISlave and MUIProcess as well as the pushMethod were intentionally disabled since the ob.framework offers better functionality while working in an Objective-C environment.
Unsupported features

The following list names unsupported functionality:

  • 3rd party MCCs - MCCs generally need to be added to the framework to become usable. Contact the SDK maintainers to find out how to get them added.
  • Objective-C only attributes aren't automagically available through the BOOPSI interface. This also implies that you cannot setup a MUI Notify on them.
  • This means that adding a 'myState' property to a class that inherits from MUINotify does not mean that there'll be MUIA_MyClass_MyState attribute supported.

    Generally, this should not be necessary at all in a Objective-C MUI application.
  • Objective-C cannot be used to create standalone MUI Custom Classes (mcc).
Debug and release libraries
Both frameworks come in 2 flavors: debug and release builds. The debug builds are meant to be used by programmers when testing as they perform a lot of extra checks and provide resource tracking - if an application leaks objects, it will give a detailed report on exit. The debug builds will only work on machines with the SDK installed.
Runtime features

Please consider the following notes:

  • exceptions: work, but the frameworks themselves will never throw
  • @synchronized: works
  • frameworks use OBAlloc/OBFree to allocate memory, several methods may require that the programmer uses those (see OBData for example)
  • method forwarding is currently unsupported
Current state of the frameworks

The current frameworks do not yet have a stable API, therefore the SDK will only allow linking them statically. This should change within a couple of months.

Examples of ObjC-MUI being in used today

As of 3.10 release: LogTool, Synergy, Zoom, Defrag. The upcoming Iris email client is written in ObjectiveC++.