Code structure and organization is a matter of pride for developers. Clear and consistent code signifies clear and consistent thought. Even though the compiler lacks a discerning palate when it comes to naming, whitespace, or documentation, it makes all of the difference for human collaborators.
Readers of NSHipster will no doubt remember the article about documentation published last year, but a lot has changed with Xcode 6 (fortunately, for the better, in most cases). So this week, we’ll be documenting the here and now of documentation for aspiring Swift developers.
Let’s dive in.
Since the early 00’s, Headerdoc has been the documentation standard preferred by Apple. Starting off as little more than a Perl script parsing trumped-up Javadoc comments, Headerdoc would eventually be the engine behind Apple’s developer documentation online and in Xcode.
With the announcements of WWDC 2014, the developer documentation was overhauled with a sleek new design that could accommodate switching between Swift & Objective-C. (If you’ve checked out any of the new iOS 8 APIs online, you’ve seen this in action)
What really comes as a surprise is that the format of documentation appears to have changed as well.
In the midst of Swift code, Headerdoc comments are not parsed correctly when invoking Quick Documentation (⌥ʘ
):
/** Lorem ipsum dolor sit amet. @param bar Consectetur adipisicing elit. @return Sed do eiusmod tempor.*/funcfoo(bar:String)->AnyObject{...}
What is parsed, however, is something markedly different:
/** Lorem ipsum dolor sit amet. - parameter bar: Consectetur adipisicing elit. - returns: Sed do eiusmod tempor.*/funcfoo(bar:String)->AnyObject{...}
So what is this not-so-strange new documentation format? After a yearlong sojourn in the lands of reStructuredText, Xcode 7 has settled on a Swift-flavored version of Markdown.
Basic Markup
Documentation comments are distinguished by using /** ... */
for multi-line comments or ///
for single-line comments. Inside comment blocks, the conventions you’ve gotten used to when writing Markdown everywhere else apply:
- Paragraphs are separated by blank lines
- Unordered lists can use a variety of bullet characters:
-
,+
,*
,•
- Ordered lists use Arabic numerals (1, 2, 3, …) followed by a period
1.
or right parenthesis1)
: - Headers can be marked with preceding
#
signs or by underlining with=
or-
. - Even links and images work, with web-based images pulled down and displayed directly in Xcode.
/** # Lists You can apply *italic*, **bold**, or `code` inline styles. ## Unordered Lists - Lists are great, - but perhaps don't nest - Sub-list formatting - isn't the best. ## Ordered Lists 1. Ordered lists, too 2. for things that are sorted; 3. Arabic numerals 4. are the only kind supported.*/
Parameters & Return Values
Xcode 7 recognizes and makes separate from a symbol’s description a few special fields. The parameters, return value, and a new “throws” section (to go with Swift 2.0’s new throws
keyword) are broken out in the Quick Help popover and inspector when styled as a bulleted item followed by a colon (:
).
- Parameters: Start the line with
Parameter <param name>:
and the description of the parameter. - Return values: Start the line with
Returns:
and information about the return value. - Thrown errors: Start the line with
Throws:
and a description of the errors that can be thrown. Since Swift doesn’t type-check thrown errors beyondErrorType
conformance, it’s especially important to document errors properly.
/** Repeats a string `times` times. - Parameter str: The string to repeat. - Parameter times: The number of times to repeat `str`. - Throws: `MyError.InvalidTimes` if the `times` parameter is less than zero. - Returns: A new string with `str` repeated `times` times.*/funcrepeatString(str:String,times:Int)throws->String{guardtimes>=0else{throwMyError.InvalidTimes}returnRepeat(count:5,repeatedValue:"Hello").joinWithSeparator("")}
A longer list of parameters can be broken out into a sublist by using the Parameters:
prefix. Simply indent each parameter in a bulleted list below.
/// Returns the magnitude of a vector in three dimensions/// from the given components.////// - Parameters:/// - x: The *x* component of the vector./// - y: The *y* component of the vector./// - z: The *z* component of the vector.funcmagnitude3D(x:Double,y:Double,z:Double)->Double{returnsqrt(pow(x,2)+pow(y,2)+pow(z,2))}
Description Fields
Swift-flavored Markdown includes another set of field headers to break out particular sections of a type or method’s description, formatted just as Returns
and Throws
above. Loosely organized, the recognized headers are:
- Algorithm/Safety Information:
Precondition
,Postcondition
,Requires
,Invariant
,Complexity
,Important
,Warning
- Metadata:
Author
,Authors
,Copyright
,Date
,SeeAlso
,Since
,Version
- General Notes & Exhortations:
Attention
,Bug
,Experiment
,Note
,Remark
,ToDo
No matter which you choose, all fields are rendered as a bold header followed by a block of text:
Field Header:
The text of the subfield is displayed starting on the next line.
Code blocks
Code blocks can be embedded in documentation comments as well, which can be useful for demonstrating proper usage or implementation details. Inset the code block by at least four spaces:
/** The area of the `Shape` instance. Computation depends on the shape of the instance. For a triangle, `area` will be equivalent to: let height = triangle.calculateHeight() let area = triangle.base * height / 2*/vararea:CGFloat{get}
Fenced code blocks are also recognized, with three backticks (`
) or tildes (~
) marking the beginning and end of a block:
/** The perimeter of the `Shape` instance. Computation depends on the shape of the instance, and is equivalent to: ``` // Circles: let perimeter = circle.radius * 2 * CGFloat(M_PI) // Other shapes: let perimeter = shape.sides.map { $0.length } .reduce(0, combine: +) ```*/varperimeter:CGFloat{get}
Documentation Is My New Bicycle
How does this look when applied to an entire class? Quite nice, actually:
importFoundation/// 🚲 A two-wheeled, human-powered mode of transportation.classBicycle{/** Frame and construction style. - Road: For streets or trails. - Touring: For long journeys. - Cruiser: For casual trips around town. - Hybrid: For general-purpose transportation. */enumStyle{caseRoad,Touring,Cruiser,Hybrid}/** Mechanism for converting pedal power into motion. - Fixed: A single, fixed gear. - Freewheel: A variable-speed, disengageable gear. */enumGearing{caseFixedcaseFreewheel(speeds:Int)}/** Hardware used for steering. - Riser: A casual handlebar. - Café: An upright handlebar. - Drop: A classic handlebar. - Bullhorn: A powerful handlebar. */enumHandlebar{caseRiser,Café,Drop,Bullhorn}/// The style of the bicycle.letstyle:Style/// The gearing of the bicycle.letgearing:Gearing/// The handlebar of the bicycle.lethandlebar:Handlebar/// The size of the frame, in centimeters.letframeSize:Int/// The number of trips travelled by the bicycle.private(set)varnumberOfTrips:Int/// The total distance travelled by the bicycle, in meters.private(set)vardistanceTravelled:Double/** Initializes a new bicycle with the provided parts and specifications. - Parameters: - style: The style of the bicycle - gearing: The gearing of the bicycle - handlebar: The handlebar of the bicycle - frameSize: The frame size of the bicycle, in centimeters - Returns: A beautiful, brand-new bicycle, custom built just for you. */init(style:Style,gearing:Gearing,handlebar:Handlebar,frameSizecentimeters:Int){self.style=styleself.gearing=gearingself.handlebar=handlebarself.frameSize=centimetersself.numberOfTrips=0self.distanceTravelled=0}/** Take a bike out for a spin. - Parameter meters: The distance to travel in meters. */functravel(distancemeters:Double){ifmeters>0{distanceTravelled+=meters++numberOfTrips}}}
Option-click on the Style
enum
declaration, and the description renders beautifully with a bulleted list:
Open Quick Documentation for the method travel
, and the parameter is parsed out into a separate field, as expected:
MARK / TODO / FIXME
In Objective-C, the pre-processor directive #pragma mark
is used to divide functionality into meaningful, easy-to-navigate sections. In Swift, there are no pre-processor directives (closest are the similarly-octothorp’d build configurations), but the same can be accomplished with the comment // MARK:
.
As of Xcode 6β4, the following comments will be surfaced in the Xcode source navigator:
// MARK:
(As with#pragma
, marks followed by a single dash (-
) will be preceded with a horizontal divider)// TODO:
// FIXME:
Other conventional comment tags, such as
NOTE
andXXX
are not recognized by Xcode.
To show these new tags in action, here’s how the Bicycle
class could be extended to adopt the Printable
protocol, and implement description
.
// MARK: CustomStringConvertibleextensionBicycle:CustomStringConvertible{publicvardescription:String{vardescriptors:[String]=[]switchself.style{case.Road:descriptors.append("A road bike for streets or trails")case.Touring:descriptors.append("A touring bike for long journeys")case.Cruiser:descriptors.append("A cruiser bike for casual trips around town")case.Hybrid:descriptors.append("A hybrid bike for general-purpose transportation")}switchself.gearing{case.Fixed:descriptors.append("with a single, fixed gear")case.Freewheel(letn):descriptors.append("with a \(n)-speed freewheel gear")}switchself.handlebar{case.Riser:descriptors.append("and casual, riser handlebars")case.Café:descriptors.append("and upright, café handlebars")case.Drop:descriptors.append("and classic, drop handlebars")case.Bullhorn:descriptors.append("and powerful bullhorn handlebars")}descriptors.append("on a \(frameSize)\" frame")// FIXME: Use a distance formatterdescriptors.append("with a total of \(distanceTravelled) meters traveled over \(numberOfTrips) trips.")// TODO: Allow bikes to be named?returndescriptors.joinWithSeparator(", ")}}
Bringing everything together in code:
letbike=Bicycle(style:.Road,gearing:.Freewheel(speeds:8),handlebar:.Drop,frameSize:53)bike.travel(distance:1_500)// Trip around the townbike.travel(distance:200)// Trip to the storeprint(bike)// "A road bike for streets or trails, with a 8-speed freewheel gear, and classic, drop handlebars, on a 53" frame, with a total of 1700.0 meters traveled over 2 trips."
Jazzy
Jazzy is a terrific open-source command-line utility that transforms your project’s documentation comments into a set of Apple-like HTML documentation. Jazzy uses Xcode’s SourceKitService to read your beautifully written type and method descriptions. Install Jazzy as a gem, then simply run from the root of your project folder.
$ [sudo] gem install jazzy
$ jazzy
Running xcodebuild
Parsing ...
building site
jam out ♪♫ to your fresh new docs in `docs`
Take a peek at a Jazzy-generated docset for the Bicycle
class.
Although the tooling and documentation around Swift is still rapidly evolving, one would be wise to adopt good habits early, by using the new Markdown capabilities for documentation, as well as MARK:
comments in Swift code going forward.
Go ahead and add it to your TODO:
list.