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:@interface MyClass : NSObject
{
int mSomeNumber;
NSString* mSomeString;
}
- (int)someNumber;
- (void)setSomeNumber:(int)aNum;
@end
I didn't think it looked easy or simple at all. Especially when I saw some code examples:
{
int mSomeNumber;
NSString* mSomeString;
}
- (int)someNumber;
- (void)setSomeNumber:(int)aNum;
@end
- (void)drawRect:(NSRect)frameRect
{
// 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];
}
The code below is just to give an idea of what Objective-C syntax is like. What the code does is not important. It has been simplified a bit to understand better. It basically draws several rows with black lines on a white background. When I first saw this kind of code I didn't think it looked easy at all. And I thought whoever said that Objective-C was a much simpler language than C++ must have been smoking something that wasn't good for them.
The problem was that I was too caught up in the syntax. The syntax is indeed very unusual, but if one looks beyond that, the structure of the syntax is actually quite simple.
Perhaps the best way to understand the syntax is to understand, why it looks so strange.
{
// 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.// Smalltalk
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);
The Smalltalk/Objective-C syntax improves readability of code. When specifying a rectangle some libraries use the start and end coordinates while others use start coordinates and size. With Smalltalk/Objective-C it is made quite clear what is done. While with C++ it is not clear whether the first or the second line of Objective-C code is used. Unlike Smalltalk Objective-C methods are called by enclosing call with []. This was originally to aid the preprocessor in extracting what was regular C code and what was Objective-C specific code.
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.
[rectangle setX: 10 y: 10 width: 20 height: 20];
If we think of Objective-C as just a preprocessor as it originally was, all message sending is replaced with a call to a C function:
id objc_msgSend(id theReceiver, SEL theSelector, ...)
So when the preprocessor encounters our rectangle message it is translated into something like this:
objc_msgSend(rectangle, "setX:y:width:height:", 10, 10, 20, 20);
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:
id (*IMP)(id, SEL, ...)
Of course this is a simplified explanation. In reality every selector is registered. Meaning we don't pass newly created strings to 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:
sel_registerName(const char *str)
Which returns the pointer if it exist or registers the selector as a unique string pointer if it doesn't exist. The benefit of this is that selectors can be looked up quickly and compared quickly by just comparing their pointer addresses rather than comparing each character of the string.
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:void objc_addClass(Class myClass);
Likewise methods and selectors are registered at runtime. So classes can in fact be modified at runtime. Of course users don't call 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
9 comments:
Good short write-up! I have started learning Objective-C as part of my graduate course in Computer Science. And I agree with you. It's indeed a very cool language.
Is it better than the D language?
I think you can seldom claim that one language is better than another. Although you can claim a language is better than another for a specific task.
I haven't used D myself, but my understanding is that it essentially is about making a better C++.
Objective-C is a very different language from C++ and has very different strengths and weaknesses. So I doubt ObjC and D target the same problen domains
I am in the midst of learning Objective C specifically to take over an iPhone project.
I enjoyed the article as it gave me a really good background on why they did the syntax they way they did it. Using the preprocessor to define a new language. I can now give them some slack for what I consider odd syntax decisions. I am sure they would have taken a different route if they were designed a language from scratch.
Already knowing C/C++/C#/Java makes this language a bit screwy to learn. Toss in an IDE that is not as full featured as the ones I use in Java and the minimal interaction between Interface Builder and XCode and the fun continues.
Seems Objective C is more cool in its ability to pull off what it did at the time it was designed and not so much what it offers a user today.
Hi,
Grate post. I just started to develop using Objective-c. I found it really cool as you mentioned. Check my latest post:
http://loadcode.blogspot.com/2007/09/why-objective-c-is-cool.html
Amir
The premise of your article is that objective C is simple. The proof you offer is that it was originally just a preprocessor. Unless you are unaware, so was C++. Infact templates, hardly the simplest part of C++, were a preprocessor for many years. I'm not saying your premise is wrong but your reasoning is flawed.
@Kevin - been right where you are now. New (and a little quirky but cool) language and an IDE (Xcode) that feels unlike any other.
It got a whole lot better though when i downloaded AppCode. It is an IDE like you're used to (although it still uses Xcode's Interface Builder)
Improved my productivity immensely (and no, I am not affiliated to Jetbrains :-).
Great article! I just started discovering Objective-C and already like it MUCH better than C++. It follows one simple rule: it just adds objective features to C and does nothing more than that. C++ tries to be "one language to rule them all", which makes it hard to comprehend. Once you get it, Objective-C turns out to be a really easy and nice programming language.
Post a Comment