The Dark Side of Cocoa
Photo by Alexander Staubo (CC)
Some time ago I wrote about all the things that I loved in the Cocoa framework. This time, I’d like to write a bit about the worse side of Cocoa – the things that annoy me, confuse me, and make me wonder who the hell came up with such an idea…
So, the things that dislike in Cocoa are:
- Long names. I know, some people say that explicit is better than implicit, also, there are no namespaces in ObjC, which may explain some of that. But I’ve seen names which really belong on The Daily WTF. The longest I’ve found so far is “NSManaged
Object Context Objects Did Change Notification” (51 characters), and I’m worried that this might not be the longest one yet… Are you telling me a name that takes half a line in my editor is more readable than a short one? Seriously?
- No literals for arrays and dictionaries. Come on, even C has array literals! So, what in Ruby and in many other normal languages would be written as:
["a", "b", "c"]
in ObjC must be written as:[NSArray arrayWithObjects: @"a", @"b", @"c", nil]
That’s 3× as many characters. And yes, with nil at the end, because ObjC isn’t smart enough to tell where a function’s argument list ends.
What about dictionaries? Ruby:
{:a => :b}
ObjC:[NSDictionary dictionaryWithObject: @"a" forKey: @"b"]
That’s FIVE times more characters. Explicit is better? Yeah, right…
- Longer code. This is closely related to the first two points. Making everything long and explicit must unfortunately result in increasing the total length of the code. And this isn’t funny. Just take a look at this example from a Core Data tutorial. It’s supposed to search in a table (or other data storage) and fetch all posts which have date later than specified, ordered by title. In Rails, it’s just one line:
Post.all, :conditions => ['created_at > ?', date], :order => 'title'
In Cocoa, it takes freaking 15 lines! You have to access the Post entity (4 lines), construct a predicate object that stores the condition (2 lines), a sort descriptor object that stores the sort order (another 2 lines), a request object, then combine the mentioned objects into the request (4 lines), execute the request, and release memory at the end (not required when using garbage collector). I’m starting to miss Java and Enterprise Java Beans…
- No default values in functions – another reason why Cocoa code can be insanely long. Functions that take many different parameters, but only require some of them, have to be called with all parameters anyway. It’s true that C or Java don’t have this feature either, but in Java I haven’t seen monstrosities like this:
[date dateByAddingYears:0 months:2 days:0 hours:0 minutes:0 seconds:0]
I wish this was a joke, but it’s not…
No layout managers. Both Swing and Qt have ways of automatically managing window layouts and positions of widgets/controls. In Swing, you can arrange components with flow layout, grid layout, border layout and so on; in Qt, there’s QVBoxLayout and QHBoxLayout for arranging widgets vertically or horizontally. These managers arrange controls automatically at runtime, so you can just add a few of them into a container and don’t think about their positioning at all.
In Cocoa, there is something called autoresizing that has similar purpose, but it works completely differently, and I think is inferior to the managers I mentioned. Interface Builder requires you to first position every single element manually in the window (although it helps a bit by showing some red lines and “snapping” controls to them), and then you have to set autoresizing and positioning properties for each element separately. You do this by defining in which directions the element will be resized and which of its borders will be moved if the parent control (the one that contains it) is resized. This works, but requires you to think on a lower level, can get messy sometimes if you change the UI dynamically, and gets totally messy once you start translating the application (more below).
I18n. In Qt, as far as I remember, localizing the interface to another language was a matter of running a script which extracted all strings from the GUI to a separate file, translating that file, and putting all translated files in a correct location. Because of the automatic layout, all widgets were automatically resized to take exactly as much space as necessary to contain a string in the current language.
And in Cocoa? You have to make a separate copy of the entire interface for each language, translate the labels, and resize and reposition all the controls *manually*. Imagine you wanted to translate a Rails app, and you did this by copying the entire application tree several times, updating all strings to a different language in each of them, and then maintaining these trees separately… Ok, maybe it’s not *that* bad – there are some console tools that can update all the copies for you, if you give them all the translations; but you still have to go through all the NIBs and make small updates to the layout… And each time you change something in the layout, like add one button or something, you have to repeat this. Brilliant.
Aaron Hillegass in his Cocoa book suggests:
(…) it is a good idea to wait until the application is completed and tested before localizing it.
I disagree. This could work if you plan to develop the application to version 1.0 without showing it to anyone, then release it, and forget about it, and never touch it again. But most applications are developed continuously, by slowly adding new features and constantly making new minor releases. If there is a point at which you can be sure you won’t be adding or changing anything, it’s probably at the moment when you abandon the application.
- Other minor stuff like:
- Poor support for version management systems – the project tree contains various huge XML files which are barely readable, or even binary files which are completely unreadable.
- No subdirectories for groups of files – if you want to separate your models, controllers and views for example, you can group them in virtual folders in XCode; however, those folders don’t exist in the filesystem. When you browse your repository, you just see a long list of files all mixed up.
- Update: I’ve just found that XCode can support directories in the project tree, it’s just not obvious. You need to create a “group”, then select “Get Info” option on it, click “Choose…” next to “Path”, create a new folder, and point XCode to that folder.
- Counting Y coordinate from the *bottom* of the window/control, not from the top. This is apparently for historical reasons… Most of the time you can change that by setting the ‘flipped’ property of a view, but bottom is the default.
That said, it’s still not as horrible as it seems. The advantages that Cocoa has over other UI toolkits still outweigh the problems. And it’s possible to work around some of the issues. What do I do when I’m coding in Ruby, and I find some API too long-winded or some code not DRY enough? I extract some functionality to a function, or do some metaprogramming tricks. I can do the same in Cocoa. Of course ObjC is not as “agile” as Ruby, but I’m pretty sure it can do a lot more that, say, Java. Java doesn’t have method_missing or open classes, and ObjC does. There are also C’s preprocessor macros available, which can do a lot of cool (and scary) things, and they do them at compile time – which means that, contrary to some Ruby tricks, they won’t slow down your application.
Also, some of these problems could be solved by using RubyCocoa or MacRuby instead of ObjC and writing wrappers for the most annoying parts of the API. Actually, the MacRuby guys are doing this right now – it’s called Hot Cocoa, and it looks pretty cool:
win = window :frame => [10, 20, 300, 300]
It would be nice to try this out in some future project…
6 comments:
Tomek Wójcik
Been there, seen that… I’m currently fighting with making my Core Data store arranged by a single column and forcing it to remember the order. Sometimes Cocoa can be very, very, very annoying. However, when you combine it with Python or Ruby (which I personally hate and avoid) you get a very powerful mixture :).
Godd luck with Mac programming.
phil
Just wanted to say that deafult argument values ARE supported in C++.
Kuba
Phil, you're right - it's been a long time since I wrote anything in C++... I've updated that paragraph.
Steve
Wow, this is a blast from the past! I'm sure you know by now, but lots of these have since been fixed or otherwise improved.
- Xcode 4.4 included a new literal syntax for arrays, dictionaries, (NS)numbers, and expressions.
- OS X 10.7 has a new auto-layout system which is comparable to layout managers in other environments.
- Auto-layout enabled them to fix the I18n situation by letting you provide ordinary "strings" files.
- Xcode 5 uses a new xib format that is drastically smaller and easier to read, and much nicer for your version control system.
The major complaint that you seem to have still is "long names, longer code". True, and NSPredicate in particular is still pretty verbose, but ARC does help a little bit in that example. Categories can help here, too.
Kuba
Hi Steve,
you're right, I'm aware of these, though I haven't used all of them yet (didn't have time to learn auto-layout yet, and new i18n is 10.8+ AFAIK). Amazing how much things have improved since then, isn't it? Literals and ARC were the biggest ones for me.
Steve
Auto-layout is pretty deep, but there's great stuff in there, and you'll want to migrate to it eventually.
Fortunately, it's not all-or-nothing: you can upgrade one view without upgrading your whole app. (It's strictly more powerful than the old system, so OS X translates classic springs-and-struts layouts into autolayout behind the scenes.)
I suggest you start with a simple view first. Autolayout is probably the biggest conceptual change from earlier versions of Cocoa, so it's easy to get overwhelmed if you bite off too much.