Using emacs for Cocoa Development

This page is a work in progress: I learned the hard way that you can't actually make a "Draft" page that's not public until you're ready for it to be, so I'm just going to work on this as I find time to do so.  I'll remove this disclaimer when it's in a shape that I find acceptable.

Introduction

I use emacs for iOS-targeted objective C development, and this document is intended as a tutorial to help you do the same if you want to.  Note that I'm an emacs user, not an emacs hacker by any stretch of the imagination.  So this document is targeted to people who don't spend much time writing emacs-lisp or hacking their .emacs files.

Why the *bleep* would you do this? It's a fair question, and one of my colleagues has gone so far as to describe my use of emacs in my day to day as "masochism."  I think that's a bit much, but I also won't pretend that my setup is a panacea, so I don't plan to gloss over aspects of this setup that are suboptimal, annoying, or downright painsauce.  But I came of age using emacs.  I can just do things in it really, really fast.  I don't have to remove my hands from the keyboard to the mouse, which is a huge time saver.  I am willing (even happy!) to trade off some of emacs' power and complexity in exchange for an IDE that simplifies my workflow.  This was, for instance, the case when I was a Java developer using eclipse.  I don't place XCode in the same category, so a couple of weeks ago I began an experiment to see to what extent I could replace it with Aquamacs Emacs.

A few words about my setup: I work on a product that I think one could fairly describe as non-typical, with dozens of XCode projects that need to import stuff from one another and that co-exist in a large directory hierarchy.  This introduces some additional complexities into my workflow that, in my opinion, make emacs even more attractive.  If you have a very simple app that fits in a single XCode project, your mileage may vary.

Coding in Obj-C

Obj-C Mode: TODO -- xcodebuild (xctool?) integration, indentation-align-to-colons

Navigating from header to code

The c-mode function ff-find-other-file jumps between header and source files.  For me, it didn't work out of the box for my projects, which contain a mix of Objective C and Objective C++.  You can define the file-to-other-file mappings for various file extensions, however, very easily.  The following says that acceptable candidates for the "other" file of a header file should include .c, .cpp, .m, and .mm files.  Voila!  And since I use this all the time, I also use a quick shortcut C-C o to jump between the two.

(setq cc-other-file-alist

      `(("\\.cpp$" (".hpp" ".h"))
        ("\\.h$" (".c" ".cpp" ".m" ".mm"))
        ("\\.hpp$" (".cpp" ".c"))
        ("\\.m$" (".h"))
        ("\\.mm$" (".h"))
        ))
(add-hook 'c-mode-common-hook (lambda() (local-set-key (kbd "C-c o") 'ff-find-other-file)))

I borrowed some functions from Brett Hutley's blog that invoke xcodebuild to make and make clean.

(defun xcode-compile ()
  (interactive)
  (let ((df (directory-files "."))
        (has-proj-file nil)
        )
    (while (and df (not has-proj-file))
      (let ((fn (car df)))
        (if (> (length fn) 10)
            (if (string-equal (substring fn -10) ".xcodeproj")
                (setq has-proj-file t)
              )
          )
        )
      (setq df (cdr df))
      )
    (if has-proj-file
        (compile "xcodebuild -configuration Debug -sdk iphonesimulator")
      (compile "make")
      )
    )
  )

(defun xcode-clean ()
  (interactive)
  (let ((df (directory-files "."))
        (has-proj-file nil)
        )
    (while (and df (not has-proj-file))
      (let ((fn (car df)))
        (if (> (length fn) 10)
            (if (string-equal (substring fn -10) ".xcodeproj")
                (setq has-proj-file t)
              )
          )
        )
      (setq df (cdr df))
      )
    (if has-proj-file
        (compile "xcodebuild -activetarget -activeconfiguration clean")
      (compile "make clean")
      )
    )
  )

SCM Integration: 

TODO git stuff, diff, merge


Autocompletion: TODO -- company mode (mention that company mode is really slow for the first file you open in a given project), dabbrev

How did I write a 100+ page PhD thesis in LaTeX without learning about dynamic abbreviation?  It makes me sad to think how much more time I might have had for MarioKart DS if I'd only known about dabbrev.  Given a partial word, type M-/, and dabbrev will dynamically search your open buffers for instances of strings that begin with the same prefix (this is in contrast to static abbreviation, where you define abbreviations yourself.  I haven't played around with this yet, but it seems pretty promising as another time saver).  For instance, in a buffer I'm working in right now I have a couple of instances where I need to generate NSNumbers from unsigned ints.  Typing NSN M-\ <space> num M-\ is sufficient for dabbrev to correctly complete NSNumber numberWithUnsignedInt. Pretty sweet!

Finding things (or: "It really is better than grep"): What does "Project Find" in XCode do?  I'll be honest: I have no idea.  At times, its results are entirely surprising to me for what they include, or (more often) for what isn't there.  This may well be related to my complex project setup, but whatever the reason, when using XCode, the end result is that I spend way too much time on the command line running ack and then opening the relevant files.

Emacs, however, has a built in support for grep, and the smart people over at shellarchive.co.uk have created an analogous mode for ack.  I put it in my $HOME/.emacs.d directory, and imported it with the following in my .emacs file:
(add-to-list 'load-path "/Users/<me>/.emacs.d/")
(require 'ack)
TODO: ack-mode usage example

Miscellaneous advantages:
  • Quit builds: The XCode "Stop sign" button might as well be renamed "SPOD for several minutes".  One advantage to my setup is that if you want to stop a build, it's instantaneous (this isn't inherent to emacs per se, but merely a side effect of the fact that you're using xcodebuild).  This enables a use case that is essentially hopeless in XCode itself: namely, start a compile, notice an error (e.g. a typo), fix that error in code and restart the compile again instantly.  This can all be done without lifting your fingers from the keyboard.
  • XCode, I just don't know how to quit you.  Oh wait, I do.  Because I need to do it a dozen times a day: It seems that there are many situations in which XCode can get confused, requiring you to kill it and restart.  Just to name two examples, updating from SCM and updates to provisioning profiles seem to be actions that will, for damn sure, require a full restart of XCode.  This is just a silly time sink.  With emacs, I never need to quit and restart my primary editor, which is a big win.

The Disadvantages:
  • No argument-aware method name completion: I haven't found a good solution for the kind of argument aware method name autocomplete that XCode does.  The screenshot below shows what I mean with placeholder argument for a very simple method, +[NSDictionary dictionaryWithObjects:forKeys:count:] ... I think we can all agree that there are much more complex selectors where the autocomplete really does come in handy.
    I spent a lot of time trying to get this working, based largely on the excellent answers to this StackOverflow question.  In fact, I thought this would be such a deal-breaker that, when I didn't get it working, I gave up on my emacs experiment.  But I'm very glad I gave it another chance, because the fact of the matter is that I don't miss this very much.  Dynamic abbreviation (plus company mode, which I'm finding that I actually use relatively rarely) is good enough to cover a surprisingly large number of cases, and even though I would prefer it if I had found a good solution to auto-completion, it's nowhere near the stumbling block I thought it would be.

Resources:


The opinions expressed here are my own and in no way reflect those of my employer.
Comments