Why do I care so much about NeXT computers? Because we at id Software developed the groundbreaking titles DOOM and Quake on the NeXTSTEP 3.3 OS running on a variety of hardware for about 4 years. I still remember the wonderful time I had coding DoomEd and QuakeEd in Objective-C; there was nothing like it before and there still is no environment quite like it even today.To get an impression of how far ahead of its time Objective-C and what is now known as Cocoa was ahead of its time, consider this:
In fact, with the superpower of NeXTSTEP, one of the earliest incarnations of DoomEd had Carmack in his office, me in my office, DoomEd running on both our computers and both of us editing one map together at the same time. I could see John moving entities around on my screen as I drew new walls. Shared memory spaces and distributed objects. Pure magic.Consider how long time ago that was, CORBA didn't support C++ for remote method invokation until 1996. At the time id did this it was just in its infant stage. Not to mention this was long long before Java RMI made remote objects populare and mainstream. It isn't even common to do this kind of thing today, and yet this was something that was quite trivial to do in Objective-C and Cocoa back in 1992.
Introducing Objective-C
Every time someone introduce you to Objective-C they will tell you about what a small and simple language it is. How easy it is to learn etc. But that was not my impression when I first tried to learn it. At the time I knew C++. When I saw an example of how to declare a class:{
int mSomeNumber;
NSString* mSomeString;
}
- (int)someNumber;
- (void)setSomeNumber:(int)aNum;
@end
{
// Fill whole background with white
[[NSColor whiteColor] set];
[NSBezierPath fillRect:frameRect];
// Construct rows of lines
NSBezierPath *gridLines = [[NSBezierPath alloc] init];
for (unsigned row = 0; row <= mNoRows; ++row) {
[gridLines moveToPoint: rowLeft];
[gridLines lineToPoint: rowRight];
rowLeft.y += rowHeight;
rowRight.y += rowHeight;
}
// Set style to draw line in
[[NSColor blackColor] set];
[gridLines setLineCapStyle:NSSquareLineCapStyle];
float pattern[2] = {2.0f, 5.0f};
[gridLines setLineDash:pattern count:2 phase:0.0f];
// Draw specified lines in given style
[gridLines stroke];
[gridLines release];
}
Background
Unlike C++, Objective-C wasn't really a new language at all originally. There was no Objective-C compiler. Instead Objective-C was just plain old C, with an added preprocessor. As any C/C++ developer worth his/her salt should know all statements starting with # specifies a preprocessor directive in C/C++. E.g.#define
and #include
. The preprocessor runs before the compiler and replaces the directives with actual C/C++ code.
What the makers of Objective-C did was to add another preprocessor, but instead of marking the directives with #, they marked them with @. That way their special preprocessor could find all the new directives easily.
While this does make Objective-C look like some alien entity inside C, it does make it trivial to distinguish between pure C code and the add-ons from Objective-C. Unlike C++, where what is C and what is C++ is blurred. Actually C++ isn't a strict superset of C like Objective-C although it usually compiles C code. The reason being among other things that C++ reinterprets a lot of regular C code into new C++ concepts. E.g. a struct isn't just a struct anymore but actually a class in C++.
Syntax inspiration from smalltalk
The second stumble block in order to understand Objective-C syntax is to realize it is derived from Smalltalk, while C/C++ syntax is derived from Algol. Algol based languages separate arguments with comma, while smalltalk separates with name of argument and colon. Below is a code snippet that demonstrates setting the position and dimension of a rectangle object.rectangle setX: 10 y: 10 width: 20 height: 20
// Objective-C
[rectangle setX: 10 y: 10 width: 20 height: 20];
[rectangle setX1: 10 y1: 10 x2: 20 y2: 20];
// C++
rectangle->set(10, 10, 20, 20);
A small and simple language
What do we exactly mean by saying that Objective-C is a small an simple language. C++ looks simpler syntax wise for the novice. However as said before syntax in deceiving. C++ add a host of new features to the C language: classes, virtual methods, non-virtual methods, constness, templates, friend classes, multiple inheritance, pure virtual functions, operator overloading, function overloading, private, public and protected members, constructors etc. Objective-C on the other hand add very little, it is just classes, methods, categories and protocols. There is only one way to create classes and to inherit form them. You can specify whether inheritance is public or private e.g. There is only one kind of methods. There is no distinction between virtual and non-virtual. They can't be const or not const. The concept of constructors and destructors don't exist. Instead these are just normal methods. As mentioned Objective-C is so simple they didn't even need to create a new compiler. A preprocessor was enough. This can make Objective-C seem overly simple. How can you do much with a language like that? The answer is the Objective-C runtime. The runtime is in fact what makes most of the Objective-C magic happen. Most of Objective-Cs features is provided at runtime and not done at compile time.Methods and Selectors
Objective-C differs from C++ in that one distinguish between methods and selectors. To call a method on an Objective-C method you send a message. The method is not called directly. Instead Objective-C determines based on the selector which method to call. Conceptually one can think of a message (selector) as simply a string with a list of arguments. The code below e.g. send the messagesetX:y:width:height:
to object rectangle
. This will invoke a method on rectangle if it understands the message.
objc_msgSend
queries rectangle for its class and then queries the class for methods it contains. Then it finds out which method corresponds to the given selector. The method is nothing but a function pointer of the form:
objc_msgSend
each time we invoke a method but a pointer to a string. This pointer has to be unique. So we can't pass any string pointer.
So if we got å string we can find the unique pointer to this string by using the function:
Unique dynamic features of Objective-C
I don't intend to go into every minor of the Objective-C runtime library but the example above should give an idea of how the dynamic features of Objective-C works.Classes
In C++ classes don't exist passed compile time. Or at least in modern C++ compilers which support runtime type identification classes exist in a watered down sense in that one can query if two classes have a relationship. In Objective-C it is almost opposite. Classes don't exist at compile time but rather are runtime entities. Classes are registered at runtime with function: objc_addClass
. These methods along with the ones that registers methods are generated by the preprocessor from the class definition provided by the programmer.
But it is this fact that classes and methods exist as structures in memory at runtime that allows the programmer at runtime to query classes about their member variables and functions and whether they respond to a selector or not.
In fact an Objective-C developer could create an application which could let user specify classes to call and functions to call. User could just type in the name of a class. Then developer could use C function NSClassFromString() to get corresponding class. NSSelectorFromString() could be used to retrieve the selector. With this one could query class further about arguments existing for selector etc.
To learn more about Objective-C look at wikipedia