A Year’s Worth of Opinions about Objective-C

posted by Craig Gidney on December 11, 2013

About one year ago, I was placed on my first Objective-C project: porting RedPhone from Android to iOS. My part of RedPhone (the backend: audio, networking, crypto) has been done for about a month now. We’re waiting for an external security review, but internally no back-end bugs have been stumbled over while work continues on the UI. When RedPhone is finally released I’ll discuss the project, what we did wrong, how our code differs from the android version, and so on. Today I’ll just be discussing my experience with Objective-C over the last year.

A year ago I’d never read a line of Objective-C. I was primarily a C# developer. Now I consider myself an intermediate level Objective-C dev: I feel comfortable in the language, but still regularly find holes in my knowledge. For example, I know that you can’t put nil into arrays and other collections, but last week I didn’t know it was convention to insert an NSNull instead. I just assumed you avoided putting nils into collections.

I tend to sound very critical when I discuss languages, so please keep in mind that this post is not an accusation that things can’t be done effectively in Objective-C. This is a post about my experiences with Objective-C, what distinguishes it from other languages, and the issues I have run into. It is my opinion, and since programming is a field filled with totally opposite opinions about what’s desirable (e.g. dynamic vs static typing) you can bet it’s only one among many. A relevant quote:

“There are only two kinds of languages: the ones people complain about and the ones nobody uses.” — Bjarne Stroustrup

Overview

Objective-C is a superset of C, so naturally the first I noticed is how unlike C it is. Basically the only C-ish stuff you do when writing in an Obj-C “style” is using header files and declaring types as if you were using them (I dislike both of those things, but that’s more a criticism of C than Objective-C).

Weirdly, the language that Objective-C reminds me of the most is not C but Visual Basic 6. At least, in terms of the problems I run into day to day. Obj-C and VB6 both love to let the program keep going. Have a strange symptom? The program probably sailed past a problem that corrupted the state. Also, both languages use reference counting to cleanup memory, which is the cause of most memory leaks you run into. Always be on guard against introducing reference cycles. Lastly, neither has generic types. Keep those types straight in your head, or you’re gonna have a bad time.

Another thing Obj-C has in common with VB is legacy baggage. For example, in Objective-C many objects have both a “core foundation” form and a “new style” “NeXTSTEP” form, the effects of introducing automatic reference counting just two years ago are still percolating through the community, and of course there’s the C subset. For the most part you can ignore the legacy baggage, but it comes up now and then.

I don’t mean to imply that programming in Obj-C is identical programming in VB. It’s really quite different. For starters, the syntax differs wildly between them.

Syntax

Objective-C’s syntax is very distinctive. To pass a message (i.e. call a method), you surround the object that will receive the message with square brackets and place the message inside the brackets. A message is made up of named parameters followed by their values, so instead of writing things like list.Insert("test", 0) you’ll write things like [mutableArray insertObject:@"test" atIndex:0].

Naming every parameter when invoking “methods” makes code a lot clearer, but does tend to balloon the code to the point where it can be hard to skim. Overall I’d say it’s a good thing. On the other hand, I think having the brackets open before the target object instead of after (like Java, C#, C++) is a mistake. It makes it harder to determine where you are in an expression, and turns simple sequences into deep nests. Extending an expression is a back-and-forth affair, because you have to keep going back to the start of a line to add “["s. XCode tries to help by guessing where the "[" goes when you type a "]“, but it’s wrong so much that it actually makes it worse.

Fortunately, Objective-C has some syntactic sugar that gets rid of nesting in many common cases. Dot notation allows you to rewrite any simple getter expression like [a value] into a.value. Similar sugar exists for setters, array indexing, and dictionary lookup. They do wonders for cutting down the verbosity of the code.

Objective-C also has container literals, which is the first thing I really liked about the language. You want a dictionary? Just type @{key:val,key2:val2,...}. There’s no autoboxing, so you have to spew @s everywhere like @{@"a":@1,@"b":@2,...}, but that’s a small price compared to what people used to have to do (lots of NSNumber numberWithInt:, a null-terminated NSArray arrayWithObjects for the values, another for the keys, and then finally mercifully NSDictionary dictionaryWithObjects:forKeys:).

One thing I did not expect Objective-C to have was anonymous functions, but it does (they’re called blocks). The block syntax suffers from a lack of type inference, so you have to write ^(int x) { return x*x; } instead of x=>x*x, but is concise enough to be usable.

Another unique bit of Objective-C syntax is using “+” and “-” prefixes to distinguish between static methods and instance methods (err.. messages). You get used to this really quickly, although it makes diffs look a little funny.

Type System

If I had to pick a thing I hate in Objective-C, it would be the type system. It can’t represent many useful types (i.e. no generics) and it doesn’t actually check your work (not even at runtime). It has a very dynamic feel, whereas I prefer strong static typing.

To start with, Objective-C doesn’t have null (well, the C part does, but you don’t really use that much). Instead, Objective-C has nil, which is like null except it won’t stop the program when you accidentally use it. Messages sent to nil don’t raise an exception, they just default to returning nil without actually evaluating anything.

I really, really dislike nil. For example, suppose you write a constructor-like method that takes a “SuperImportantSecurityChecker” parameter. You assert that it’s not nil, because it would be pretty bad for the security checks to just spuriously not be performed! You also write a test that purposefully causes the security failure, and check that things do in fact fail. It’s a good thing you did, because you forgot to initialize the SuperImportantSecurityChecker field with the SuperImportantSecurityChecker parameter’s value! A mistake like that could go undetected for years, thanks to how nil works.

Another example of the program just continuing when things have gone wrong is the startling lack of runtime type checks. For example, if you write NSMutableArray* v = [NSArray array] then Objective-C will happily point your mutable array pointer at an immutable array (you used to not even get a compile warning because NSArray.array returned id; now it returns instancetype). When you try to call addObject on the “mutable” array, the program will crash with a “selector not found” error. This isn’t nearly as bad as an unexpected nil, which would just silently discard the item-to-be-added instead of crashing, but it’s still annoying to track down these mistakes.

The lack of runtime checks is particularly nasty when working with blocks, because the language allows you to freely over-specify input types. It’s so tempting to write [array enumerateObjectsUsingBlock:^(NSNumber* obj, NSUInteger index, BOOL *stop) { ... }];, based on your expectation that obj should always be a number instead of the more general id, but ultimately you’re going to mess up sometimes and have to track down the issue. I’ve taken to strewing assert([obj isKindOfClass:[WhateverType class]]) lines at the start of most blocks just to pre-emptively catch these mistakes.

Another notable way Objective-C can silently fail is if you forget particular compiler flags. You might already know that failing to specify “-ObjC” can result in category methods not being accessible at runtime, despite being found at compile-time, but did you know that not including “-fobjc-arc-exceptions” results in ARC not cleaning up properly if an exception is thrown? This is justified by conventions strongly favoring you not catching exceptions, and apparently the speed benefits are non-negligible, but it still blows my mind that Apple has their language default to incorrect behavior.

Basically, I constantly feel like Objective-C’s type system is fighting me. I’m trying to write a secure voip app intended to protect you from your oppressive government, but the language I’m using is practically designed to upgrade trivial errors into ongoing compromises instead of immediate crashes. I guess I’ll just never ever make any stupid mistakes and that way no one will die. Right? Just be perfect. Or someone gets tortured. … Maybe I’ll check it just ten more times …

XCode

The editor you use has a huge impact on how you experience a language. The go to editor for Objective-C is Apple’s XCode, which is what I use day to day. There’s also JetBrains’ AppCode, although it’s not free and I’ve only logged a few hours on it in total.

If I was going to rate XCode, I’d give it a 3 out of 5. It is mediocre. There are a lot of niceties, like the fantastic way it highlights matching braces and search results (they sort of bounce out with a burst of yellow) or the useful inline bubble hints left after auto-completing a method, but there’s a lot lacking.

My major gripe is, oddly, the format of the project file. I swear, it is designed to cause merge conflicts. I don’t know if it’s the UUIDs associated with every item, the repeated information, or the multiple details on every line… but I can’t remember the last time I merged without having to fix the &*(&ing project file by hand.

Interestingly, there’s an upside to the project constantly breaking. The quickest way to fix the project is to just revert it, and then fix the group containing your source code by dragging the corresponding folder into the project to recreate the group (which works because the source files merged just fine). This regular recreation keeps your groups synced with the file system, so that your project doesn’t look like a mess in external environments like github. How “convenient”.

My next issue with XCode is auto-completion. It’s not good at it. In particular, when you have half-written code above where you’re editing you’re almost guaranteed to see partial results. This tricks you into thinking basic methods don’t exist, because you’re staring right at an apparently complete list that does not include them. Another situation where XCode’s autocomplete is noticeably bad is when you use the dot syntax: it almost never works. Type a space and you get lots of results, including getters, but type a dot instead and you’ll probably see no results at all. Frustrating.

The last gripe I have with XCode, that’s worth mentioning, is the limited refactoring functionality. You’re limited to renaming variables, and a few other things, and that doesn’t even work well. It’s really slow (I’ve seen it take minutes), and half the time XCode just crashes instead. Honestly, you should probably just install AppCode if you’re going to do any refactoring. Either that or get used to abusing find+replace.

Summary

Objective-C is an okay language. It has some nice syntactic sugar and a usable go to editor, but (from the perspective of someone who likes static typing) the type system leaves a lot to be desired.

At least, that’s been my personal experience.

Discuss on Reddit

My Twitter: @CraigGidney

  • Nate Pendleton

    I have had very similar experiences, coming from C/C++/C#/Java background. It sometimes seems like the worst of both static and dynamic worlds. To be fair, I think the named parameters for messages is an improvement over my main languages, and there probably some cool things that can be done with messages/selectors that I don’t know about.

    • fredfnord

      A lot. You can do amazingly ugly hacks with them (because you don’t have to know anything about an object to send it a message), which I’m not normally a big fan of, but if I can save a couple pages of repeated boilerplate code with two lines (normally ‘respondsToSelector’ and ‘performSelector’) it’s worth it.

      • Nate Pendleton

        I figured I would probably only use those when handling optional message of a protocol.

  • http://en.dutras.org/ Leandro G Faria Corcete DUTRA

    XCode? GNU Emacs.

    • Mickey_disqus

      Aye. I spent the last four years writing in Objective-C for a cross-platform target (various versions of Win and Linux) and found that most people in the office tended to end up using either EMacs (or XEmacs for some), or Vim.

  • Kyle Thielk

    Well said. I’m much earlier on in my Objective-C learning curve (coming from a primarily Java background) and the syntax certainly takes some getting used to. Named parameters for method calls are a godsend. Dealing with methods that take multiple arguments takes significantly less jumping back and forth in code. No more memorizing the order of arguments.

  • fredfnord

    Good lord. If you want an array (or set or dictionary or whatever) that you can rely on to contain only one type of object, create a ‘FFTypedArray’ and in the initializer give it a type, and when objects are added check their types. The whole thing is like twenty lines of code. And you don’t have to strew isKindOfClass any more.

    • CraigGidney

      Sure, I’ll have an FFNumberArray and an FFStringArray and an FFNumberArrayArray and an FFUIViewArray and an FFBlockTakingIntReturningIntArray and an FFStringToNumberArrayDictionary … and an … and an… … …

      then, for each of those types, I’ll specialize all of the helper methods that access or modify them so that they take just the right type. I’ll specialize copy and initWithArray and firstObject and last object and objectAtIndex and objectsAtIndex and reverseObjectEnumerator and containsObject and indexOfObject and enumerateObjectsUsingBlock and suBArrayWithRange and arrayByAddingObject and firstObjectCommonWithArray and …

      I think you get the point. The absurd amount of code duplication required to do generics by hand makes it non-viable except in extreme cases. You’re also creating maintenance work to keep the derived array class synced with NSArray as changed are made to it in future updates to the framework.

      • Vitali

        You wouldn’t create a collection per type. You would create a single collection that takes a Class as an argument. Then everywhere you instantiate with/add objects, you verify that [anObject isSubclassOfClass:self.enforcedClass]. Of course, this means you have to provide init methods to take an additional type parameter & override existing init methods to throw an exception.

        You can do something similar with protocol by checking [anObject conformsToProtocol:self.enforcedProtocol];

        • CraigGidney

          That can be a useful technique, but it’s very limited. It can detect adding the wrong type of object, but it can’t detect reading out an object and expecting it to be the wrong type. It also won’t work in doubly-generic situations (e.g. grouping users by last name into a dictionary gives you a dictionary containing arrays of users, but there’s no array of users class to pass in). Also it doesn’t detect the errors at compile time.

          • fredfnord

            So we’ve gone from ‘what an absurd idea, ha ha ha’ to ‘that can be a useful thing to do, but it’s very limited’. That’s nice. I’d respond further if I thought you would put the requisite 15 seconds of thought required to actually understand my response, instead of simply reacting defensively. Instead I will simply say that I think you’re selling the approach short, and leave it at that.

          • CraigGidney

            The first response was me misunderstanding your suggestion, so I apologize for that. The second response applies, as far as I can tell.

            I didn’t put just 15 seconds of thought into it. I’ve actually tried to use this before. Actually, I took it further than you implied. I defined a type to represent nested generic types like ‘array of array of NSNumber’. After awhile I discarded it because it wasn’t worth the effort. Every method was starting with an expensive block of “and now we actually check the types!” for not enough gain. Plus it wasn’t going to catch mistakes like assigning an apparently type-checked array argument to the wrong field, catching mistakes despite the given array being empty in the current dev situation, catching mistakes before the code can be run, etc.

          • Vitali

            If you want super strong type safety, then I would recommend ObjC++ & STL collections. I really only recommend it though if you’re using ARC as otherwise you’ll have to manually remember to retain before inserting & release when erasing.

            vector strings or vector weakStrings;

  • jernfrost

    Thanks for keeping an open mind. So often I read C# and Java developers pour so much scorn and hate on Objective-C and xCode. For just one years experience I’d say you had a pretty good experience. For me it is the other way around. I find C# and Java to typically be clunky at wiring GUIs to logic. I fully admit that Objective-C has a lot of weaknesses. But it also has a number of considerable strengths. I think Cocoa is an exceptionally well designed framework and I believe a lot of that can be attributed to the nice way Objective-C naturally supports delegates, bindings, key-value-coding, key-value-observation, Object graph systems such as Core Data, distributed objects and undo systems. With the dynamic nature of Objective-C and categories I find it much easier to keep inheritance hierarchies shallow than in C++, Java and C#.

    xCode has poor refactoring support I’d give you that, but with respect to organization and navigation I’d say it is a really well designed IDE. In many ways it is the opposite of something like Eclipse. Eclipse is much stronger in features, but a total mess with respect to UI, navigation and organization.

    I think the key to stop worrying about the lacking static type system in Objective-C is to start treating Objetive-C as a dynamic languages like Python. Static types in Objective-C are mainly there to assist you as a developer and give you warnings about possible wrong usage of objects. They are completely ignored at runtime.

  • Malcolm Teas

    Obj-C type system is really the C type system. C is a lower-level language than some people are used to today. Generic types and runtime checking are overheads not suitable for a language like C. Obj-C is essentially a layer on C so shares many characteristics.

    • Danny Gratzer

      Actually the lower level the language, the more I want compile time checking. Generics don’t have to have any impact on the runtime, but providing compile time gaurentees is a god-send when you’re doing something like writing a filesystem.

  • Marmat Kat

    Thanks for sharing. Something you may not realize (I didn’t see you mention this, just a comment about square brackets): “Objective-C derives its object syntax from Smalltalk” – http://en.wikipedia.org/wiki/Objective-C

  • josho

    You might be interested in this: https://github.com/tomersh/Objective-C-Generics


Twisted Oak Studios offers consulting and development on high-tech interactive projects. Check out our portfolio, or Give us a shout if you have anything you think some really rad engineers should help you with.

Archive

More interesting posts (1 of 8 articles)

Or check out our Portfolio.