4 Ways Precompiled Headers Cripple Your Code

When used well, a precompiled header can save you precious compilation time. But when used poorly, precompiled headers can hide problems in your source code that you may not notice until you try to reuse parts of it for another project.

This post is part of the Code Smells in Objective-C series.

Precompiled headers were invented for one purpose: to make compiling faster. Rather than parsing the same header files over and over, these files get parsed once, ahead of time. Speed is important! The faster you compile, the faster you can complete the feedback loop to see if recent changes were successful.

In Xcode, you do this by including the header files you want in a “prefix header,” and enabling “Precompile Prefix Header” so they get precompiled. But the idea behind a prefix header is different from precompiling. A prefix header is implicitly included at the start of every source file. For example, if your prefix header is Prefix.pch, it’s like each source file sneaks

#import "Prefix.pch"

at the top of the file, before anything else. This can be handy for project-wide #defines. (Just remember that in general, #defines are a code smell.)

It’s also handy for precompiled headers. The fact that every source file includes these precompiled headers is an artifact of being in the prefix header.

And this is where things start to go wrong…

Precompiled headers don’t exist to save you typing!

Apple’s iOS project templates start you off with Prefix.pch including Foundation and UIKit. From a compilation speed point of view, this makes a lot of sense. The problem is that people noticed and said, “Those files are already implicitly included. So I don’t need to include them again.” Upon discovering this side-effect, some programmers start dumping more headers into Prefix.pch. Because hey, then you don’t have to #import it ever again.

The purpose shifted from “make this project compile as fast as possible” to “save myself some typing.” A Stack Overflow question reflects this, asking, “Why have both?” Even the Wikipedia entry for prefix header reflects this incorrect conclusion: “As a result, it is unnecessary to explicitly include any of the above files.” This misunderstanding is widespread.

And it’s flat-out wrong.

Four problems of over-relying on precompiled headers

The problem is that to successfully compile a file, it’s no longer enough to have the paired header (.h) and the implementation (.m). You also need Prefix.pch — not because they’re precompiled, but because they’re implicitly included.

“So?” you ask. “What does prevent you from doing?” Basically, you end up creating incomplete source files. There are at least four ways this can cause problems:

1. Source files can’t be copied to different projects

Say you’ve added <QuartzCore/QuartzCore.h> to your prefix header. A particular source file uses QuartzCore. Try copying that source to a different project.

Chances are good that it won’t compile, because the other project has a different set of precompiled headers. You’ve managed to create a nonportable source file!

2. Dependencies are hidden

One of the benefits of any system of importing other files is that it reveals the file’s dependencies. You can scan the beginning of a .h or a .m file and see what other files it uses. This gives you a quick sense of its scope.

Not so if your imports are implicitly bound up in the prefix header.

3. Dependencies are buried

A large project may have a large number of precompiled headers. Say you’re looking at a source file, and trying to find its dependencies. You’re clever enough to realize that earlier programmers relied on precompiled headers to save typing, omitting many #imports. So you look at the prefix file as well.

But if Prefix.pch has more than a handful of #imports, which ones does your source file need? All of them? None of them? Some of them? Which ones?

4. Dependencies get out of hand

Even if you make all #imports explicit, it’s easy to create an explosion of file dependencies. Keeping the dependency tree tamed is hard enough.

But if no effort has gone into a) making all #imports explicit, and b) taming them, these dependencies can silently grow out of hand. Dependency rot can spread unnoticed, for years — until it’s too late. Suddenly you’re working on a new project and have no clean way of reusing earlier code, without bringing it all in as a massive, wasteful glob of cruft.

Find and fix the missing #imports

Because of the way Xcode married the prefix header with precompiled headers, omitting #import statements is a common Objective-C code smell. But it’s an unusual one, because the smell itself can go unnoticed for a long time. (Silent but deadly!)

To fix the problems, you have to find the problems. And to find the problems, you have to temporarily remove the smell-stopper:

  1. Edit your prefix file. Temporarily comment out all #imports and #includes. (Select them all and Command-/ to comment them all at once. Repeat this to uncomment them.)
  2. Try building your project. You’ll see the problems right away.

The larger the project, the longer it’ll take to do this first pass of fixes. If you get tired, set it aside and resume the cleanup later. But I urge you to get your project clean. Making dependencies explicit is an important first step in reducing them.

Question: Have you tried this cleanup? How bad was it? Leave a comment below.

Did you find this useful? Subscribe today to get regular posts on clean iOS code.


About the Author Jon Reid

Jon is a consultant on Clean Code for iOS, focusing on Test Driven Development, unit testing, refactoring, and design. He's been practicing TDD since 2001. You can learn more about his background, or see what services he can bring to your organization.

follow me on:
Disclosure: The book links below are affiliate links. If you buy anything, I earn a commission, at no extra cost to you.
  • David F. says:

    I can so relate to this pain. I had a major project that we had to change the “common” or shared layer. Doing so we had to update the .pch and then I spent 4 hours putting in all the needed #imports. The joy was the compiler would list the first three .m files that did not compile, add the needed #imports, compile again. I had to touch over 200 files!

  • Paul says:

    I agree on abuse of pch.
    However two cases I find them handy:

    1. most projects I drop <Foundation/Foundation.h> and <UIKit/UIKit.h> imports as it pollutes the sources. It’s just my preference for the speed of reading import statements to drop those 2 common headers on iOS projects.

    2. include DLog macro to add debug logging anywhere in the project without needed to change imports. DLog is useful for Debug configuration and stripped on Release. pch includes DLog.h with handy macros.

  • Are you suggesting to import only Foundation.h and UIKit.h in the precompiled header and thus import all other headers explicitly in the source file? Or are you suggesting to import some headers both in the precompiled header and in the source file?

  • George Cook says:

    I agree in some respects; but I think this post should’ve been more tempered.. especially that apple themselves promote the benefits of using the pch for compile times (see this year’s wwdc videos; many references to this))

    Like everything in software engineering – abuse is possible, I’m not sure you did enough here to point out the benefits; readers who are newer to objective c will likely interpret this post as “don’t use pch files” which is about as dumb as saying “don’t use singletons”.

    • Jon Reid says:

      OK, so to make it clear: I use precompiled headers in all my projects. My purpose in doing so is to reduce compile time.

      But every so often, I comment out everything in my prefix to see if the project still builds. It should.

  • SG says:

    So, you’re exchanging the certainty of doing more work now to avoid the possibility of doing the same amount of work in the future? The only advantage to this is that you might be faster at it since the source material is fresh in your mind.

    If there’s no bloat the the binary (the question whose researching brought me here), there’s no objective disadvantage, and the pros outweigh the listed cons:
    1- clarity of included libraries and support headers at-a-glance (at the pch file)
    2 – brain-free addition of new imports – they always go in the pch, no thought required
    3 – the primary benefit: if you’ve got something you need project-wide – a category on NSString for example – you only import it once

    … perhaps other minor ones as well related to ease of editing and maintenance.

    Mainly, I think the approach recommended here is symptomatic of the belief that you can construct a “perfect” source file: whitespace just right, comments on every line, precisely named methods and variables, everything organized in groups with pragmas, use of @public, @private, class extensions, private categories, internal subclasses, documented protocols which are only used once, and internally in the class at that, etc…. that is, spend at least 20% of your time on every project on the part no one will *ever* see – probably not you either.

    Now, on a refactor, for some code to be explicitly reused, the benefit can be worth the cost. But as a matter of routine, this is hardly a synergistic practice. The shortest path from A to B is the correct answer. “Real artists ship” and all that.

    • Jon Reid says:

      Hi SG,

      You seem focused on my first point (the added difficulty of reusing source files). Maybe you’ve never reused something, and haven’t experienced the pain. But you make it sound like I’m doing a lot work. With AppCode, it’s quite easy to remove unused imports, and have it add missing imports.

      But I think you’re missing the larger point, brought out in my remaining points. Coupling may be the greatest problem in software projects. Coupling must be continually held in check, and file dependencies are the most visible evidence of coupling. I use https://github.com/nst/objc_dep to keep an eye on my file dependencies. It doesn’t do rigorous semantic analysis; it just looks at import statements. So I rely on my import statements to tell the whole truth about my file dependencies. Even when I’m not using the tool, the number of import statements alone serves as an important clue about whether the class is violating the Single Responsibility Principle.

  • Luis says:

    Totally agree on the dependency hell that involves using PCHs. Having to explicitly include headers lets you visualize dependencies as well as improve compilation time since your changes won’t trigger recompilation of the whole project. I wrote an Xcode plugin to relief of importing .h in your project: https://github.com/lucholaf/Auto-Importer-for-Xcode

  • Rohit says:

    Thanks man, your answer saved my time and money……

  • Eljay says:

    An additional problem to the buried dependencies is when the precompiled header turns into a precompiled cesspool of most-or-all of the project headers.

    Changing any one header causes all the files to be recompiled. Because everything is dependent on the precompiled header which depends on most-or-all the headers.

    Thwarting the initial purpose of the precompiled header in the first place: to making compiling faster.

    Precompiled headers should only be used for very, very stable headers. Such as the C++ standard headers, and a stable library like Boost headers, and the OS headers.

    • Jon Reid says:

      Gosh yes Eljay! You point out something that’s become second-nature to me: Do Not Precompile Any Headers That Change Often. Thank you for pointing that out, it’s a source of frequent pain.

  • >