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.

—

—

—



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