Producing Documentation with appledoc
It's late at night, you're tired, and you're stuck on a complex problem you just can't seem to solve. In your moment of need, you turn to Google, and after unleashing your expert search skills, you spot it - a GitHub project shimmering on the horizon that looks like it solves your problem. You hit that clone button faster than is decent and open the project. The solution is close, but not quite right for your situation. As you attempt to modify it, you hit wall after wall of misunderstanding. With only the code to go on, what looked like an oasis slowly fades, revealing itself to be nothing more than a mirage. Demoralised and even more tired, you try a different approach.
What a missed opportunity - for both you and the original developer.
When we share our code, we often do so hoping it will prove useful to someone else. But code alone is rarely enough. The next developer needs the thinking behind the code. They need context. Documentation is that context. But it's not just a tool to help the next developer - it's also a reflection point. If you sit down to describe a type or method and struggle to explain it clearly, that's a signal that something isn't right. It's probably doing too much. The overly long comment block becomes a refactoring prompt as much as a communication tool. So write documentation for the next developer, but also write documentation to enable you to write better code.
So how do we build that oasis and avoid the mirage? Let's start with a tool that makes producing documentation straightforward - appledoc.

appledochasn't seen active development since 2021, and the project is currently looking for contributors. It remains a solid tool for Objective-C projects, but there may now be better alternatives out there.
Getting to know appledoc
appledoc is a command-line tool that turns your Objective-C source code comments into Apple-style HTML documentation and Xcode docsets - helping us write better code by writing documentation. Add specially formatted comments to your code, point appledoc at your project, and it handles the rest - parsing your source to produce documentation that looks and feels like Apple's own.
It can be installed via Homebrew:
brew install appledoc
appledoc is deceptively simple - it has very few keywords and operates mainly by string matching. Before we delve into an example, let's look at the syntax that powers appledoc.
appledoc works by parsing /** */ comment blocks placed above declarations.
Note the extra opening
*to differentiate anappledoccomment from a standard Objective-C comment.
The text that sits within an appledoc comment block can be:
- Multi-lined.
- Contain lists.
- Formatted, e.g. use
* *to style the enclosing text as bold. - Contain URLs to external sources.
Each type can have the following documentation sections:
- Overview.
- Discussion.
- Task (Properties) list.
- Instance methods.
- Class (Type) methods.
- See also.
Not all these sections need to be present, e.g. a type without class methods won't have that section.
The keywords in appledoc:
| Keyword | Description |
|---|---|
| @param |
Describes the name and purpose of a method parameter |
| @return |
Describes the method's return value |
| @see |
Populates the "See also" section for the type or method with a link to another type |
| @warning |
Describes a warning about the method |
| @bug |
Describes a bug within that method |
appledoc extracts these values when building the documentation and provides a different visual representation for each one.
Now, let's look at how we can use appledoc to write documentation.
Adding Signposts
In this example, we are going to add documentation to two types that are part of a camel racing game: ADECamel and ADETrack. Undocumented, the interface of these types looks like:
@interface ADECamel: NSObject
@property (nonatomic, assign) NSUInteger currentSpeed;
- (id)initWithRaceNumber:(NSUInteger)raceNumber
topSpeed:(NSUInteger)topSpeed
rider:(NSString *)rider;
- (void)run;
@end
@interface ADETrack: NSObject
@property (nonatomic, strong) NSArray * camels;
- (ADECamel *)race;
@end
Let's start revealing the mystery of these types, starting with ADECamel.
Classes
A class can include a single Overview section describing its purpose.
To add an Overview section, we add a comment block directly above the @interface declaration:
/**
Represents a camel competing in a race.
*/
@interface ADECamel: NSObject
//Omitted other functionality
@end
An Overview can be spread over multiple lines:
/**
Represents a camel competing in a race.
Tracks the camel's current speed as the race progresses and provides access to race information such as the camel's assigned race number and rider.
*/
@interface ADECamel: NSObject
//Omitted other functionality
@end
To keep the examples focused, I've omitted any functionality that isn't directly related to where the documentation is being added.
Now that we have seen class documentation, let's look at property documentation.
Properties
Property documentation follows the same pattern as class documentation:
@interface ADECamel: NSObject
/**
The camel's current speed in km/h. Updated throughout the race as the camel accelerates and decelerates.
*/
@property (nonatomic, assign) NSUInteger currentSpeed;
//Omitted other functionality
@end
If a property doesn't have a comment block, appledoc won't pick them up as a class "Task" or include them in automatic cross-references (we'll see more on that later).
Now that we have property documentation, let's look at method documentation.
Methods
A method can have multiple sections in its documentation. Let's start by adding an Overview section:
@interface ADECamel: NSObject
//Omitted other functionality
/**
Designated initialiser.
*/
- (id)initWithRaceNumber:(NSUInteger)raceNumber
topSpeed:(NSUInteger)topSpeed
rider:(NSString *)rider;
//Omitted other functionality
@end
In method documentation, the first line becomes the Overview section. Anything after a blank line becomes the Discussion section:
@interface ADECamel: NSObject
//Omitted other functionality
/**
Designated initialiser.
Creates a camel object ready to compete in a race.
*/
- (id)initWithRaceNumber:(NSUInteger)raceNumber
topSpeed:(NSUInteger)topSpeed
rider:(NSString *)rider;
//Omitted other functionality
@end
The Discussion section does not appear in the Quick Help option in Xcode (only in the docset), so don't put anything critical into this section - use it as an opportunity to expand on points already made.
By default, the
Overviewsection is part of theDiscussionsection, but if you don't want that, use the--no-repeat-first-parflag when producing your documentation - we'll see how to use flags soon.
It's looking better, but we are still missing some details about the method, so let's add in documentation for the parameters:
@interface ADECamel: NSObject
//Omitted other functionality
/**
Designated initialiser.
Creates a camel object ready to compete in a race.
@param raceNumber the camel's assigned race number
@param topSpeed the camel's maximum speed in km/h
@param rider the name of the camel's rider
*/
- (id)initWithRaceNumber:(NSUInteger)raceNumber
topSpeed:(NSUInteger)topSpeed
rider:(NSString *)rider;
//Omitted other functionality
@end
The @param
value must match the method signature parameter name exactly. A mismatch won't stop the build, but it will trigger a validation warning and may break cross-referencing.
Let's add the final missing piece of documentation for this method - the return type:
@interface ADECamel: NSObject
//Omitted other functionality
/**
Designated initialiser.
Creates a camel object ready to compete in a race.
@param raceNumber the camel's assigned race number
@param topSpeed the camel's maximum speed in km/h
@param rider the name of the camel's rider
@return a camel object configured for the race
*/
- (id)initWithRaceNumber:(NSUInteger)raceNumber
topSpeed:(NSUInteger)topSpeed
rider:(NSString *)rider;
//Omitted other functionality
@end
With the initialiser documented, let's finish the run method by adding a @warning:
@interface ADECamel: NSObject
//Omitted other functionality
/**
Starts the camel moving.
@warning *Warning:* Once started, a camel will never stop running.
*/
- (void)run;
@end
Now that we have some documentation, let's talk about the superpower of appledoc: automatic cross-referencing.
Cross-referencing
Good documentation lets you move through it. appledoc enables this with automatic cross-referencing by turning recognised type names into links. We don't need to use any keywords to enable this functionality; we just need to ensure that the type string exactly matches the type. In our ADETrack implementation, we have a method that returns ADECamel, which will result in cross-referenced documentation being produced:
@interface ADETrack: NSObject
//Omitted other functionality
/**
Starts the race and returns the winning camel.
@return the ADECamel that won the race
*/
- (ADECamel *)race;
@end
In the @return statement, we simply mention ADECamel and appledoc will do its magic.
appledoccan't link through to Apple's own documentation, so if, for exampleUIAlertViewDelegatewas mentioned in the documentation, the stringUIAlertViewDelegatewon't become a link.
Finally, let's see everything together:
/**
Represents a camel competing in a race.
Tracks the camel's current speed as the race progresses and provides access to race information such as the camel's assigned race number and rider.
*/
@interface ADECamel: NSObject
/**
The camel's current speed in km/h. Updated throughout the race as the camel accelerates and decelerates.
*/
@property (nonatomic, assign) NSUInteger currentSpeed;
/**
Designated initialiser.
Creates a camel object ready to compete in a race.
@param raceNumber the camel's assigned race number
@param topSpeed the camel's maximum speed in km/h
@param rider the name of the camel's rider
@return a camel object configured for the race
*/
- (id)initWithRaceNumber:(NSUInteger)raceNumber
topSpeed:(NSUInteger)topSpeed
rider:(NSString *)rider;
/**
Starts the camel moving.
@warning *Warning:* Once started, a camel will never stop running.
*/
- (void)run;
@end
/**
Represents a race track and manages the camels competing on it.
Provides the ability to start a race and determine the winner from the competing camels.
*/
@interface ADETrack: NSObject
/**
A collection containing the camels competing in the race.
*/
@property (nonatomic, strong) NSArray * camels;
/**
Starts the race and returns the winning camel.
@return the ADECamel that won the race
*/
- (ADECamel *)race;
@end
Everything above applies equally to class methods, protocols and categories.
Building the Oasis 🌴
There are two ways to drive appledoc:
- Command line with flags.
- Command line and plist containing flags.
Both approaches use the same flags:
| Flag | Description |
|---|---|
| --project-name | The name of the documentation. This name is case sensitive, so "AppledocExample" and "appledocExample" will produce two separate sets of documentation. |
| --project-company | Company name. |
| --company-id | Reverse domain, expanded to form the docset Bundle ID, e.g. com.williamboles becomes com.williamboles.AppledocExample.docset. |
| --output | The folder used to create the docset before it's moved to Xcode. |
The above flags are the minimum flags needed for
appledocto produce documentation.appledochas a lot more built-in flexibility, just waiting for you to explore it.
Command Line with Flags
From the project folder, run:
appledoc --project-name AppledocExample --project-company "WilliamBoles" --company-id com.williamboles --output ~/help .
Don't forget the
.- it tellsappledocto look in the current directory.
Command Line with Plist
The plist approach uses the same flags, just stored in a file - handy if you're running appledoc regularly and don't want to retype them each time.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>--company-id</key>
<string>com.williamboles</string>
<key>--project-company</key>
<string>WilliamBoles</string>
<key>--project-name</key>
<string>AppledocExample</string>
<key>--output</key>
<string>~/help</string>
</dict>
</plist>
From the project folder, run:
appledoc .
Don't forget the
.- it tellsappledocto look in the current directory for the plist.
Don't be the Mirage
Someone, somewhere, will hit a problem that your code solves. It might be a teammate, a stranger on the internet, or you at midnight six months from now. The difference between your project being the mirage and being the oasis is documentation. Give them the thinking behind the code. Give them the context. Let them modify, adapt, and build on what you've made.
Don't be the missed opportunity.