The OpenStep specification has been published for some time now and both NeXT and Sun are starting to deliver systems that are based around OpenStep. Converting existing programs to OpenStep, according to Steve Jobs at ObjectWorld in August '95, will take considerable effort. He stated that a 50,000 line project should take about one man-month to convert from NeXTSTEP to OpenStep.
It is quite clear from the documentation that OpenStep is another example of NeXT introducing an incompatible upgrade. In the past when they have done this, after a short bedding in period users have accepted that the change has been an improvement.
The two questions that come to my mind are: is OpenStep really an improvement for developers over NeXTSTEP, and how much effort is really required to convert to OpenStep?
To provide an answer to these questions, I took an existing application written for NeXTSTEP 3.2, that didn't take advantage of FoundationKit, and converted it to conform to the OpenStep specification. That application was DialOnDemand, which made use of some common NeXTSTEP objects, as well as some less common ones. It used the Defaults system, an NXBrowser, NXTypedStreams, Workspace notification, and included a StringList class taken from the NeXT supplied MiniExamples.
The Defaults system has had a major overhaul in OpenStep. IN NeXTSTEP, it was built around a number of function calls to retrieve and save values. OpenStep replaces this with an NSDictionary to contain the values. To initialise my defaults, I use:
NSMutableDictionary *defaults = [NSMutableDictionary dictionary];
[defaults setObject:@"/etc/ppp/bin/pppd" forKey:@"Command"];
[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
This sets up a dictionary with default values for the keys I plan to use. To read values, I can use:
[[NSUserDefaults standardUserDefaults] setObject:[command stringValue] forKey:@"Command"];
and to read it back again, I would just use:
[[NSUserDefaults standardUserDefaults] objectForKey:@"Command"];
This returns an NSString object, with I can convert to a char* string if required with the cString message.
One of the biggest areas of change in OpenStep from NeXTSTEP is the transition from char* C style strings to the object based NSString. The NSString object itself implements many of the extensions commonly required in programming, such as case conversion, searching and character set aware comparisions. It has the obvious disadvantage of being much slower to use that C strings, although in many cases this is not material.
The purpose of using the StringList class, which was a subclass of Storage, was that I required an ordered collection of strings, for the purpose of iterating over the set and finding a match for a simple C string. This class allowed easy loading into a browser for display and editting purposes.
My solution for OpenStep was to replace this class, rather than converting it, with a NSMutableSet. Adding and deleting an object is simple enough:
- (void)addFileName:sender;
{
if ([[fileName stringValue] length] > 0)
[fileList addObject:[fileName stringValue]];
[fileScroller loadColumnZero];
...
[fileName selectText:self];
}
- (void)delFileName:sender;
{
[fileList removeObject:[fileName stringValue]];
[fileScroller loadColumnZero];
...
[fileName selectText:self];
}
Scanning for a match is also simple, using NeXT's enumerator approach:
NSEnumerator *i = [fileList objectEnumerator];
id string;
while (string = [i nextObject]) {
NSRange range = [sampleString rangeOfString:string];
if (range.length != 0) {
system([[[NSUserDefaults standardUserDefaults] objectForKey:@"Command"] cString]);
}
}
I find the enumerator, which operates identically for a number of other classes, including NSArray, an elegant contruct to use. Similarly the NSRange object, used for substring matches as above, works well in the context of Unicode strings.
One consequence of choosing to drop StringList was the need to fill a browser from the object. The simplest way of doing this is to implement a delegate method, browser:createRowsForColumn:inMatrix:, which takes over from a similarly named NeXTSTEP method. Here is my implementation:
- (void)browser:sender createRowsForColumn:(int)column inMatrix:(NSMatrix *)matrix;
{
NSEnumerator *cellLoop, *fileLoop = [fileList objectEnumerator];
NSString *string = nil;
NSBrowserCell *theCell = nil;
// Set matrix to have the right number of cells.
[matrix renewRows:[fileList count] columns:1];
// For each cell set its value, set whether it is a leaf or not
cellLoop = [[matrix cells] objectEnumerator];
while((string = [fileLoop nextObject])
&& (theCell = [cellLoop nextObject])) {
[theCell setStringValue:string];
[theCell setLeaf:YES];
}
}
This is slightly simpler than the old method, and basing it upon a pair of enumerators works very well.
I managed this in the original project by using NXTypedStreams, and by telling the fileList object to write: to that stream. Very simple.
With OpenStep, NeXT introduce the concept of an archiver, which abstracts the concept of a NXTypedStream to a more general level. I initialise my fileList object now by:
filePath = [[[NSBundle mainBundle] pathForResource:@"fileList" ofType:@""] retain];
NS_DURING
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
fileList = [[NSUnarchiver unarchiveObjectWithFile:filePath] retain];
} else {
fileList = [[NSMutableSet setWithCapacity:10] retain];
[NSArchiver archiveRootObject:fileList toFile:filePath];
}
NS_HANDLER
NSRunAlertPanel(0, @"Unable to open file name archive inside app wrapper", @"OK", 0, 0);
[NSApp terminate:self];
NS_ENDHANDLER
Writing to the object once it is set up is still easy:
[NSArchiver archiveRootObject:fileList toFile:filePath];
In common with most other changes, this is slightly simpler and easier to handle compared with the original. In this case, Unix-like handling of the stream has been removed to the Archiver, where it belongs.
There are many cases in NeXTSTEP where an application wants to be informated of an external event. This is most commonly handled by delegates, although the disadvantage of using a delegate is that each sender can have only one delegate. There are many cases where we would expect a number of object to receive notification of an event. OpenStep includes a much more general mechanism, known as a notification. We can register our interest in application launches by:
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self
selector:@selector(appNotice:)
name:NSWorkspaceWillLaunchApplicationNotification
object:nil];
This will message appNotice: in our app (self) for all app launches. A notification can be removed by:
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSWorkspaceWillLaunchApplicationNotification
object:nil];
The appNotice: method is called with an NSNotification object, which can be used to pass some user information over. In this case, it passes a dictionary containing some standard keys, so we can include in appNotice:
[[notification userInfo] objectForKey:@"NSApplicationName"]
Pervasive in OpenStep is the change from having most methods return self, to requiring the majority of methods to return void. Also pervasive is the change of most methods that used to return a char* to return an NSString.
Along the way to OpenStep, NeXT introduced many of the changes required in a half-way house, FoundationKit. Many existing apps can improve their memory management by implementing release/retain/dealloc.
There is a lot of work rewriting applications using standard objects to conform to OpenStep. Some of this is routine work, but many of the changes are based on subtle paradigm shifts, and will require intelligent rewriting of code. There may be many new objects included in OpenStep that an old application can be greatly improved by using.
Overall, I find the new API more elegant and consistent. Once familiar with OpenStep, writing new applications will be a level more easy (once again).
One factor to bear in mind: DialOnDemand is a small application, which took 1.5 days to write. Converting it to OpenStep (and learning along the way) took 2 days. Expect relative conversion times to be smaller for more standard applications, larger projects and more experienced programmers, but it is still a major work.
You can inspect the original DialOnDemand application, or download full source to DialOnDemandOS.
Paul_Lynch@plsys.co.ukDesigned by Paul Lynch
using WebPages 1.7
Last updated: April 24, 1997