rentzsch.com: tales from the red shed

10 Things I Love about ObjC
and
15 Things I Hate about ObjC
(a skeptic's introduction to Objective-C)

You've heard both extremes. ObjC is way better than YourBrand language. ObjC lost the language wars like ten years ago.

Perhaps you'd like a little perspective?

Here's a list capturing my ObjC experience, good and bad. I've shopped it around to PSIG, CAWUG and SFU, integrating the feedback I received from: 1) folks who don't know ObjC at all, 2) those learning it and 3) those who've mastered it.

I try to present both sides when I feel there's a controversy. In particular, each Hate point has an associated Why? part, where I attempt to provide an explanation of why the issue exists (be warned: there are a few cases where I did not find a reasonable explanation). Items presented in order of importance.

: )
Love

  • Love 1: Lightweight

    Basically* ObjC is C plus a preprocessor and a small runtime. Indeed, except for the OO stuff, ObjC is C. Thus, ObjC benefits greatly from C's ocean of compiler vendors, books, community, robustness, OS support, etc. It can go most places C can go -- and C gets around, baby.

    * Yes, I know this isn't how ObjC is currently implemented. Here I'm willing to wave my hands a little to communicate the desired mind set.

  • Love 2: Compatible

    Since ObjC is C plus Objects, it's very compatible with C (and now C++) code bases. (There is a gotcha concerning mixing exceptions ObjC and C++ exceptions -- see my NSXException class for details.)

    Not having to rewrite the world when using another language is nice.

  • Love 3: (Fairly Good) Runtime Object Model

    It seems like I've been waiting forever for a real runtime object model on the desktop. COM is lame, and SOM was apparently too heavy. ObjC is here, and is actually being used.

    ObjC's object model runtime is highly pragmatic: it's simple and fast, but still provides good metadata for introspection. It does most everything you want (I'll readdress this in a Hate point).

  • Love 4: Message Sending

    ObjC has real, fast, runtime message sending. Indeed, the entire language hinges on it.

    Coupled with ObjC's language design, message sending can replace many uses of multiple inheritance. While I'm in the Eiffel camp (I believe multiple inheritance is not inherently evil), I will concede all mainstream languages with multiple inheritance support tends towards suck. It's nice you can get many MI benefits without having to bring up MI (with its associated controversy).

  • Love 5: Categories

    Categories allow addition of methods to classes after the class's initial definition. This helps in at least three ways:

    1. Code Generation Lube. Here, you can have one file generated by software, which can be overwritten at anytime. Your additions go into another related -- but sacrosanct -- file. Thus it makes it easier for machines and humans to coexist. Microsoft has been playing this up lately with C# 2.0's "partial types".
      I fail to get very excited about this since I believe -- in general -- if you're doing textual code generation, you're doing something wrong. However, I also understand it seems currently unavoidable in today's programming environments. Even so, I don't think it buys you much over the Generation Gap pattern (which I use to great success with Java, EOF and EOGenerator).
    2. Class Extension without the Class Bottleneck. Often you want to add a convenience method to an existing class, but your method doesn't really belong on the class. Perhaps the Class is in a reusable cross-application framework, and your convenience method is only useful to one app.
      Here, your app could add your convenience method as a category on the main class, without modifying the main class at all. Handy. Especially since this works even on Classes whose source code you don't have. For example, it's easy to add, say a rot13 method to Apple's system-supplied NSString class. (Trivia bit: iChat's InstantMessage.framework contains a category named NSViewShouldHaveAVisibleBitLikeEveryOtherFrameworkInExistance on NSView which adds visible and setVisible: methods. I fully agree guys, it's wacky we had to wait until 10.3 for this bit.)
    3. Overriding Possibilities. Not only can you add a method, you can redefine an existing method's implementation. This allows tricks like fixing system framework bugs in your app, tracing execution and sundry hacking tricks. This feature is not without suckage, which I will address in a later Hate point.

  • Love 6: Static Typing

    Unlike many other dynamic languages, ObjC offers static typing of variables, with all the associated early-error-detection goodness. For example, if you send NSMutableArray's removeObjectAtIndex: message to a NSView*, your friendly compiler will issue a compile-time warning that it "may not respond to" the message. This warning saves me on a regular basis, and is perhaps the biggest reason I enable the "treat warnings as errors" compiler option. Without this option, if I screw up and then cmd-r, the build proceeds past the error, the app is launched and either dies immediately or I crash the app myself by not noticing the error.

    There are times when you just want fire a message to an object, regardless of its type. That's what id is for. You're telling the compiler "I don't know this object's type". Nice and explicit. (A bonus is that you don't have to explicitly cast to id when assigning from another pointer type. I openly wept when CodeWarrior Pro 6 broke the implicit casting of <type>** to void** back in 2001.)

  • Love 7: Key Value Coding

    Key Value Coding (KVC) allows getting and setting an object's state knowing little else except the names ("keys") the object supports.

    In the KVC world, nearly every object can be treated like a dictionary -- you can get and set values based on names. Behind the scenes, KVC will call accessors/mutators for you (based on the name, following a well-defined set of rules), even accessing instance variables directly.

    Thus, KVC enables data-driven uniform object state access. Sit and let that soak in for a second. And that's the tip of the iceberg -- witness Key Value Paths, Key Value Observing and Key Value Binding.

  • Love 8: #import

    Saves the traditional #include preprocessor dance:

    #ifndef __ARMOR__
    #define __ARMOR__
    // code goes here
    #endif

    It's insane this wasn't part of the C or C++ standard like ten years ago. I understand the reasons why you can't kill #include outright (rarely you need to be able to include a file more than once), but there's no good technical reason #import couldn't be bolted on.

  • Love 9: NSBundle's -classNamed

    NSBundle's -classNamed: method makes it very easy to load a class by name. Exactly the way it should be. Contrast to C++, which makes it very difficult for no good reason.

  • Love 10: Preprocessor

    ObjC inherits C's preprocessor, enabling the standard set of unhygienic macro tricks and offering the standard macro symbols: __FILE__, __LINE__, __PRETTY_FUNCTION__, etc.

    Conditional compilation rocks, and makes things like compile-time assertions and compile-time code selection possible. Standard Java lacks conditional compilation and suffers mightily for it.

: (
Hate

  • Hate 1: Method-call Syntax

    I hate ObjC's method-call syntax, for both political and technical reasons.

    Political: The syntax is a consistent point of argument, draining resources on those who would teach the language, and those who would learn the language.

    The would-be teachers seem to develop an intellectual callous from the constant complaining, making them appear elitist and unable to appreciate other, genuine issues raised against their language. The syntax also harms those who attempt to learn the language. Newbies are usually unsure, learning ObjC on their own time. The weird syntax is just an obstacle. Pity the poor newbie who dares complain -- the crack team of ObjC bigots will be sure to flame him to C#.

    The syntax might be worth the political costs... but it's barely any better!

    Technical: There are really two issues here: keyed parameters and brace syntax. I consider keyed parameters a definite "win". It's really too bad ObjC doesn't make better use of them (such as allowing parameter reordering and per-parameter default arguments [which would be a big win over C++]), but I greatly prefer them to "naked" parameter lists. And you can have keyed parameters even without Smalltalk-style message sending -- Python is but one example.

    No, it's the brace syntax where my hate lies. Do you really believe:
    [[[MyClass alloc] init:[foo bar]] autorelease]

    is easier to read or write than:
    MyClass.alloc().init(foo.bar()).autorelease()

    It's funny, I screwed up typing the first example, but not the second. ObjC isn't Lisp -- the brace syntax buys you nothing in terms of language tricks.

    Why? Language grammar integration. The brace syntax is all about assisting the compiler to delimit an ObjC message send from the otherwise "normal" C code. Thus, it's there to aid the machine, not the programmer. I will say one good thing about the brace syntax: because it makes nesting method calls painful, it encourages conformance to the Law of Demeter.

  • Hate 2: Lightweight

    Yes, this was also Love #1. This is a love/hate thing. The flipside of being little more than C is that ObjC doesn't help you much. Specifically, you'll notice ObjC code makes heavy use of idioms since:

    • The same code must often be repeated.
    • There's little language support for anything other than message sending.

    The ObjC guys think all this explicit coding is good. They'll appeal to your desire for simplicity by telling you there's "no magic" in ObjC. Don't buy it. There's a long road from the explicitness of ObjC to, say, the terseness of Perl. Making the common case more convenient won't going to kill you. Some syntactic sugar is good.

    Why? ObjC guys say "no magic". I view this as a reverse-engineered explanation. Instead, ObjC has simply failed to evolve much as a language since its inception as a simple superset of C. I submit the necessary evolution has been carried largely by the framework (NextStep/OpenStep/Yellow Box/Cocoa), not the language. Granted, the two are so intertwined as to be an almost academic distinction (see Indifferent #1), but I will stand by my assessment of the language itself: ObjC is a mediocre language propped-up by a great framework.

  • Hate 3: Pointers

    ObjC gets raw pointers from its C heritage.

    Raw pointers are evil and must be stopped.

    At the very least, we need thin wrappers over raw pointers. C++ gets the idea right, if with a characteristically prickly implementation. Also, raw pointers preclude good garbage collection.

    More importantly, I had a hard time justifying learning a new language that lacks garbage collection. This is coming from a guy who knows manual memory management. Pity the newbies coming from scripting languages.

    Why? C/C++ compatibility.

  • Hate 4: Alloc/Init Dance

    Modern ObjC separates object allocation and initialization. This is not wrong by itself. Indeed, it would be wrong if you couldn't separate them. However, it is wrong that all code everywhere must separately call both +alloc and -init, in the right order.

    Here, "no magic" == "more code" == "more bugs". Except for the dumb syntax, C++ gets this right (placement new).

    Why? No good reason (yes, I know about +new).

  • Hate 5: Designated Initializers

    Back on the "no magic" meme, an ObjC class has a designated initializer: an initializer all other initializers should call through.

    ObjC provides zero lingual support for this very important indicator. You're left with optional, nonstandard comments and/or heuristics indicating such an initializer.

    Why? No good reason.

  • Hate 6: Initialization Idiom

    ObjC's initialization idiom is simply shocking:

    if( self = [super init] ) {
      // initialization code here
    }
    return self;

    While [super init] is fine, assigning to self is disturbing. I haven't coded in another language where overwriting your object context pointer was possible or commonly used. Even more disturbing is the assignment can fail. Yikes!

    In addition, ObjC coders seem partial to placing assignments in if and while statements, relying on the resulting implicit boolean condition. This idiom masks a common error that would otherwise be easily flagged by the compiler: unintentional assignments where conditions are expected. Thus, I recommend this code instead:

    self = [super init];
    if( self ) {
      // initialization code here
    }
    return self;

    Yes, it's one more line of code. But now your compiler can flag these kinds of errors:

    if( a = b ) { // should be a == b
      // do something
    }

    The compiler wants to help you write error-free code. Let it help.

    Why? On assigning to self: Unlike, say, C++ (which an instance has only one "this" pointer) ObjC instances have multiple, scoped "selves." As for the assignment-for-condition idiom: we're-so-kewl coupled with compiler monotony.

  • Hate 7: No Stack Objects

    You can't allocate ObjC objects on the stack (anymore -- this was possible in the early days). Even when you could, the benefit wasn't there since ObjC lacks destructors or a guarantee -dealloc would be called.

    As much as C++ sucks in general, I really appreciate its resource initialization is acquisition (RIIA) idiom. Its transactional semantics makes writing exception-safe code easier/safer. However, it requires stack-based objects, which are "magic". Yeah, it's "magic" like compiler-generated register management is "magic" -- that is, not at all.

    Why? No good reason (ObjC++ wrappers can do this).

  • Hate 8: Hard to Write Good Getters/Setters

    Getters and setters are surprisingly hard to get right in ObjC.

    You need to decide if your instance variable should be handled like a reference (which can be shared) or a value (which cannot). Most of the time you want value semantics, but implementation efficiency often makes folks choose reference semantics. Your decision effects how you write your getters/setters. Had to figure this out for myself, as I never found any good explanation of it.

    Not only do you have to deal with the reference/value semantics issue, but reference counting, threading and exceptions also complicate the issue.

    Why? Most of this complexity stems from the lack of automatic memory management.

  • Hate 9: Preprocessor

    Like Lightweight, this too was on my Love list.

    The C preprocessor is terribly useful, but inherently evil. Like raw pointers, it must die. Unlike Java, it must be replaced, albeit with something safer (like hygienic macros).

    Why? C/C++ compatibility.

  • Hate 10: Messaging nil

    In ObjC, sending a message to nil does basically nothing.* This greatly reduces the need for checking for nil pointers all the time. Less code == less bugs, right?

    Unfortunately, it also does a great job of hiding real bugs. Accidently disconnected outlets in shipping Cocoa apps are legend. It wouldn't be so bad if you could make messaging nil scream. However, it's hard to do, and screams all the time since Cocoa messages nil as a matter of course.

    * It actually does do something -- it returns nil.

    Why? Cocoa errs on the side of expression ease at the expense of early error detection. I won't say that's wrong, but I don't think that tradeoff really needs to be made.

  • Hate 11: Class unloading

    While you can load a class at runtime, you can't unload it. Dude, where's my fully dynamic runtime object model? Dynamic class unloading would enable one heck of a dynamic development experience.

    Why? Because, with all the class and method lookup caching, "it would be hard".

  • Hate 12: Overriding

    You can't add instance variables using categories or posing. There are inefficient work-arounds. But come on, let's get a real metaobject protocol runtime going.

    Categories are broken out-of-the-box for overriding. Overriding a method more than once leads to undefined behavior. Yes, there's work-arounds for this as well.

    Why? C/C++ compatibility mostly.

  • Hate 13: No Namespaces

    Namespace support is an elegant answer when integrating disparate libraries. ObjC doesn't support this at all, so everyone's prepending their class names with near-random initials in the hopes we don't collide.

    Why? Stems from C, but perhaps could piggyback on C++ namespace support.

  • Hate 14: id should be id*

    Not so much a hate, more a distaste. Less clear:
    NSString *foo = @"foo";
    id bar = foo;

    More clear:
    NSString *foo = @"foo";
    id *bar = foo;

    The second example's pointer assignment to bar is more explicit. All in all, strange for a language that is otherwise explicit about everything else. Likewise, Class should be Class*

    Why? No idea. Maybe "code cleanliness"?

  • Hate 15: NeXTie Arrogance

    The entire "if you're not 100% enthusiastic about ObjC syntax, then you're stupid" gets real old, real fast.

    Fortunately this message, as relayed from Apple, died with Rhapsody. I don't think it's coming back -- Apple has lost the language religion, and they're better for it. They're actively welcoming C, C++ and Java coders.

    No, my gripe lies with the ObjC old-timers. Withstanding the Hate points above, you've got a neat language. But it's not without flaws, and one of them is syntax. I know you guys have been conditioned, when faced with complaining, to either stop listening or flame without mercy. You're right there are those who are just looking for an excuse to dismiss your language. However, nestled alongside the ignorant complaints is legitimate feedback on how to make your language better. Stop listening at your lingual peril.

    At the end of the day, it's Cocoa that's the crown jewel, not the language.

: /
Indifferent

Not everything about ObjC elicits passionate response.

  • Indifferent 1: Frameworks

    You can't really use a completely alternative framework with ObjC. I'd care if Cocoa wasn't the best one publicly available.

    I reserve the right to change my mind when Adobe open sources their frameworks. (grin)

  • Indifferent 2: Memory Management

    Cocoa uses reference counting. I can handle manual memory management, and I can obviously handle automatic memory management. However, Cocoa's model is kind-of manual, kind-of automatic.

    This was a big stumbling block for me, and would have been high on the Hate list. The best way I could describe it was a car with an automatic transmission and a clutch. The car would shift for you, but you'd need to depress the clutch at the right times. Do it right, and it's one less thing to do. Do it wrong, and you throw your engine through the hood. For all the danger, I just wanted my simple manual transmission back.

    What saved me is I finally learned how pervasive NSAutoreleasePool is. Initially, I thought it was a special-case hack. Instead, it's used everywhere, all the time. Indeed, if you squint your eyes, via autorelease pools and pervasive idioms, you can pretend ObjC has garbage collection.

    The bottom line is the docs suck, but retain/release isn't fundamentally evil. Not good, but not bad.

    (And yes, I do know about the Boehm-Demers-Weiser garbage collector...)

Conclusion

ObjC isn't my favorite programming language, but it definitely has its moments. I fear ObjC will lose due to its syntax and reliance on pointers. Fortunately, it seems I needn't fear the death of the great framework it spawned: Cocoa.

First, Apple has a previous example of moving a Cocoa-like framework off Objective-C: WebObjects (now pure Java). Second, PyObjC is starting to look compelling as a method of using Cocoa without writing any ObjC code yourself.

Even if ObjC dies, if Cocoa lives on, then its best part will have been retained...


Update: First, I failed to give credit to some folks who richly deserve it. I totally ripped off the title from Miro Jurisic and his excellent "Top 10 Things I Love About STL" and "Top 10 Things I Hate About STL" sessions at MacHack 2003. Also, Chris Hanson gave me some ideas that helped populate both sides of the list. In addition, it was through his training that I finally realized how pervasive NSAutoreleasePool is.

Both Erik Barzeski and Michael Tsai followed up with great points (be sure to scroll down on Erik's entry for great rant from bbum on how ObjC does not have named parameters!).

Finally, Eric Albert alerted me to a typo and a more serious error. I wrote that messaging nil returns nil. While technically correct, it's only part of the story. Messaging nil returns 0 in PowerPC register r3. However, if the method was supposed to return a structure (NSRect is the most common), you get garbage back. Non-nice. I knew there was something related to this, but I couldn't remember the details, and mistakenly thought it had to do with returning doubles. I couldn't reproduce it, so I kept my mouth shut. Better to be thought a fool and all that...

Saturday, November 29, 2003
12:00 AM