Cocoa Documentation.png

Cocoa 代码注释与文档生成

本文的文档规范部分的内容参考自:NSHipster 的 Swift Documentation 作者 & Nate Cook

本文知识目录

Cocoa 代码注释与文档生成目录.png

背景

曾经以为好的代码是可以自我解释不需要注释的,后来发现不是这样的。就算是个人项目,代码注释的重要性也是毋庸置疑。毕竟人的记忆只有七秒!

一个开发者是从其他语言转到 Cocoa 开发,大都会被它冗长的代码所 😺 到,Objective-C 的冗长使得其代码就是有效的自我说明。有如 stringByAppendingPathExtension: 以及参数的显式类型,这样的方法一般不会给人留下太多的想象空间。不过 Swift 就有所不同了。

但即使是自我说明的代码也可以通过说明文档加以改进,而且只需少量的努力就可以给别人产生显著的好处。

文档生成工具

每一个现代编程语言都有注释:由一个特殊的字符序列标记的非可执行的自然语言,如 ///**/--。使用特殊格式注释的文档,提供了代码的辅助解释和上下文,可以用编译工具提取和解析。

从 00 年代早期,Headerdoc 就一直作为苹果首选的文档标准。从 Perl 脚本解析勉强的 Javadoc 注释作为出发点,Headerdoc 最终成为了苹果在线文档及 Xcode 中的开发者文档的后台引擎。

HeadDoc风格的文档

随着 WWDC 2014 的发布,开发者文档被翻修并重新设计,包含了 Swift 和 Objective-C 的支持。

Apple 新风格的文档

appledoc

类似 Javadoc, appledoc 能够从 .h 文件生成 HTML 和 Xcode 兼容的 .docset 文档。

Doxygen

主要用于 C++,但一般在 iOS / OS X 的开发者社区不很受待见。

Jazzy

支持 Objective-C 和 Swift,通过对 Clang 和 SourceKit 的 hook 方式获取代码 AST 树来精准获取注释。

Cocoa 文档规范

就像许多苹果开发者生态系统一样,Swift 改变了一切。 本着 “与时俱进,与新共存” 的精神,Xcode 7 将Headerdoc 换成了粉丝最喜欢的 Markdown,尤其是 Swift 风格的 Markdown。

Xcode 中生成 document 模版的快捷键为 option(alt) + cmd + /

Xcode 添加注释的快捷方式

下面为最基本的 Cocoa 注释。

Objective-C 风格的注释:

1/// Let's say something
2///
3/// @param bar I'm paramter
4/// @return I'm return value
5- (id)fooBar:(NSString *)bar;

Swift 风格的代码注释:

1///
2/// Let's say something
3///
4/// - Parameter bar: I'm paramter
5/// - Returns: I'm return value
6func foo(bar: String) -> AnyObject { ... }

Swift 注释的提示效果

基本标记规则

文档注释通过使用 /** ... */ 的多行注释或 ///... 的单行注释来进行区分。在注释块里面的内容支持标准的 Markdown 语法,规则如下:

  • 段落由空行分隔
  • 无序列表可由多个项目符号字符组成:-+*
  • 有序列表使用阿拉伯数字 1,2,3,… 后跟一个点符 1. 或右括号 1) 或两侧括号括起来 (1)
  • 标题使用 # 作为前缀,内容的分割线使用 ---
  • 支持 链接图片,会将基于Web的图像下载并直接在 Xcode 中显示
 1/**
 2 Hello, documentation
 3
 4 [土土Edmond木](https://looseyi.github.io/)
 5
 6 # Lists
 7
 8 You can apply *italic*, **bold**, or `code` inline styles.
 9
10 ---
11
12 # Unordered Lists
13 - Lists are great,
14 - but perhaps don't nest;
15 - Sub-list formatting...
16 - ...isn't the best.
17 ---
18
19 ## Ordered Lists
20 1. Ordered lists, too,
21 2. for things that are sorted;
22 3. Arabic numerals
23 4. are the only kind supported.
24 */

预览效果

Markdown 注释的样式效果.png

摘要和说明

文档注释的开头部分将成为 “摘要“ Summary,余下的其他内容将一起归入 “讨论” Discussion

如果文档注释以段落以外的任何内容开头,则其所有内容都将放入讨论中。

参数和返回值

Xcode可以识别一些特殊字段,并使它们与符号描述分开。 当将 ParameterReturnThrows 置为项目符号项后带上冒号 : 时,它们会在快捷提示和帮助文档中与 讨论 分段。

  • Parameters: 行首以 - Parameter <param name>: 开始并带上参数的描述
  • Return values: 行首以 Returns: 开始并带上返回值的描述
  • Thrown errors: 行首以 Throws: 并配上可能抛出的异常描述,由于 Swift 不会对超出错误一致性的抛出错误进行类型检查,因此正确记录错误尤为重要。

本文我们基本以 Swift 代码作为示例。如果代码是 Objective-C,格式稍微有一些区别。

以返回值的关键字为例。在 Swift 中,我们编写 -return :,但在 Objective-C 中,您将编写 @return。 有关更多详细信息,请参见 Apple 的 HeaderDoc 用户指南

 1/**
 2 Creates a personalized greeting for a recipient.
 3
 4 - Parameter recipient: The person being greeted.
 5
 6 - Throws: `MyError.invalidRecipient`
 7           if `recipient` is "Derek"
 8           (he knows what he did).
 9
10 - Returns: A new string saying hello to `recipient`.
11 */
12func greeting(to recipient: String) throws -> String {
13    guard recipient != "Derek" else {
14        throw MyError.invalidRecipient
15    }
16
17    return "Greetings, \(recipient)!"
18}

如果你的方法有多个参数的话,可以通过 Parameters: 字段加上无序列表符来注释:

 1/// Returns the magnitude of a vector in three dimensions
 2/// from the given components.
 3///
 4/// - Parameters:
 5///     - x: The *x* component of the vector.
 6///     - y: The *y* component of the vector.
 7///     - z: The *z* component of the vector.
 8func magnitude3D(x: Double, y: Double, z: Double) -> Double {
 9    return sqrt(pow(x, 2) + pow(y, 2) + pow(z, 2))
10}

其他字段

除了上面的常用字段,Swift 风格的 Markdown 还定义了其他几个字段:

类型
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

可以按以下方式松散地组织它们,每个字段在快速帮助中均显示为粗体标题,后跟一段文本:

- 字段名: 字段描述的内容从下一行开始

代码块

文档注释也支持嵌入 Code 来演示功能的正确用法或实现细节。插入代码可以通过添加三个反引号 (```) :

1/**
2 The area of the `Shape` instance.
3
4 Computation depends on the shape of the instance.
5 For a triangle, `area` is equivalent to:

let height = triangle.calculateHeight() let area = triangle.base * height / 2

1*/
2var area: CGFloat { get }

或者三个反波浪号 (~~~):

 1/**
 2 The perimeter of the `Shape` instance.
 3
 4 Computation depends on the shape of the instance, and is
 5 equivalent to:
 6
 7 ~~~
 8 // Circles:
 9 let perimeter = circle.radius * 2 * Float.pi
10 // Other shapes:
11 let perimeter = shape.sides.map { $0.length }
12 .reduce(0, +)
13 ~~~
14 */
15var perimeter: CGFloat { get }

MARK / TODO / FIXME

在 Objective-C 中,我们通过预处理器指令 #pragma mark 将功能划分为有意义的,易于导航的部分。 在 Swift中,可以通过注释 // MARK: 实现相同的功能。以及我们常用的其他 tag:

  • // MARK:
  • // TODO:
  • // FIXME:
  • // NOTE:

为了展示这些新标签的实际作用,下面介绍了如何扩展 Bicycle 类以采用 CustomStringConvertible 协议并实现 description属性。

Mark 分割效果

 1// MARK: - CustomStringConvertible
 2
 3extension Bicycle: CustomStringConvertible {
 4    public var description: String {
 5        var descriptors: [String] = []
 6        ...
 7       
 8        // FIXME: Use a distance formatter
 9        descriptors.append("...")
10
11        // TODO: Allow bikes to be named?
12        return descriptors.joined(separator: ", ")
13    }
14}

给一个类添加完整的注释

 1/// 🚲 A two-wheeled, human-powered mode of transportation.
 2class Bicycle {
 3    /// Frame and construction style.
 4    enum Style {
 5        /// A style for streets or trails.
 6        case road
 7
 8        /// A style for long journeys.
 9        case touring
10
11        /// A style for casual trips around town.
12        case cruiser
13
14        /// A style for general-purpose transportation.
15        case hybrid
16    }
17
18    /// The style of the bicycle.
19    let style: Style
20
21    /// The size of the frame, in centimeters.
22    let frameSize: Int
23
24    /// The number of trips traveled by the bicycle.
25    private(set) var numberOfTrips: Int
26
27    /// The total distance traveled by the bicycle, in meters.
28    private(set) var distanceTraveled: Double
29
30    /**
31     Take a bike out for a spin.
32
33     Calling this method increments the `numberOfTrips`
34     and increases `distanceTraveled` by the value of `meters`.
35
36     - Parameter meters: The distance to travel in meters.
37     - Precondition: `meters` must be greater than 0.
38     */
39    func travel(distance meters: Double) {
40        precondition(meters > 0)
41        distanceTraveled += meters
42        numberOfTrips += 1
43    }
44}

完整的 🚴‍♀️ 文档,戳这里

Library 项目文档的生成

接下来我们一起来实践一下,如何使用 Jazzy 来生成项目的 API 文档。

首先,Jazzy 支持对 Swift 和 Objective-C 的 Xcode project 项目生成文档,对于 SwiftPM 也是近期刚支持的。我们会逐步进阶,从一个独立的 Swift 和 ObjC 项目,再到混编项目,最后是基于 CocoaPods 来生成私有库文档。

为 Swift 项目生成文档

Jazzy 默认是解析 Swift 代码注释来生成文档。我们先尝试为 Alamofire 生成 HTML 文档。

首先需要安装 Jazzy,Jazzy 是一个 Gem 依赖库,更多了解 Gem 请看 《版本管理工具及 Ruby 工具链环境》

1[sudo] gem install jazzy

不过由于 Alamofire 在项目的 Gemfile 中已经添加了 Jazzy 的依赖,所以我们这里使用 bundle install 来安装。最后通过 -o Docs 将文档输出到项目的根目录,完整命令如下:

1$ git clone https://github.com/Alamofire/Alamofire
2$ cd Alamofire
3$ bundle install
4$ bundle exec jazzy -o Docs

生成文档的效果:

Jazzy 生成文档效果.png

Jazzy 默认会读取项目中的 README.mdREADME.markdownREADME.mdownREADME 文档内容作为 index page 的内容。

对于 Swift module 项目,Jazzy 在生成文档时会自动寻找项目根目录下的 project,然后执行 xcodebuildswift build 来生成 Clang AST,再通过 SourceKit 来生成注释文档。

对于支持 SwiftPM 的项目,我们也可以通过指定 --wift-build-tool 来生成文档:

1$ bundle exec jazzy \
2  -o Docs \
3  --module Alamofire \
4  --swift-build-tool spm \
5  --build-tool-arguments -Xswiftc,-swift-version,-Xswiftc,5

为 Objective-C 项目生成文档

Objective-C 项目文档的生成需要多几个参数,这里我们以 AFNetworking 为例。

clone 代码到本地后需要修改 AFNetworking 目录下的 Gemfile 添加 Jazzy 依赖:

1source "https://rubygems.org"
2
3gem "fastlane"
4gem "cocoapods"
5gem "jazzy"
6gem "xcode-install"

接着安装 jazzy 生成 docs 文档:

1$ bundle install
2$ bundle exec jazzy \
3  -o Docs \
4  --objc \
5  --umbrella-header AFNetworking/AFNetworking.h \
6  --framework-root AFNetworking
  • –objc:指定为 Objective-C 生成文档
  • –umbrella-header:指定 Objective-C framework 的 Umbrella header 路径
  • –framework-root: 指定 Objective-C framework 路径

为 Swift & Objective-C 混编项目生成文档

Jazzy 对于混编项目的支持目前还不是很友好。想要生成混编代码注释的文档,我们需要事先生成 json 格式的中间产物,再传递给 Jazzy 来生成完整的文档。这里需要用到工具 sourcekitten

先看基于混编项目生成文档的方式:

 1# 输出 SourceKitter 的 Swift 文档数据到 json
 2sourcekitten doc -- -workspace MyProject.xcworkspace -scheme MyScheme > swiftDoc.json
 3
 4# 输出 SourceKitter 的 Objective-C 文档数据到 json
 5sourcekitten doc --objc $(pwd)/MyProject/MyProject.h \
 6      -- -x objective-c  -isysroot $(xcrun --show-sdk-path --sdk iphonesimulator) \
 7      -I $(pwd) -fmodules > objcDoc.json
 8
 9# 合并 swift 和 Objective-C json 并输出文档
10jazzy --sourcekitten-sourcefile swiftDoc.json,objcDoc.json

当然 Jazzy 还有其他很多使用参数这里就不一一介绍了,大家感兴趣的可以查看官方文档或者使用 jazzy --help <command>

总结

  • 本文介绍了如何按照 Apple 的标准写出 Swift 风格的代码注释,以及基于 Jazzy 来生成简单的 HTML 文档。
  • 也许大家平时不太注意文档的规范和书写,但是当我们需要查看系统 framework 或者是开源的 API 时总觉得注释里的说明不够详尽,所以从现在开始写好文档吧。
  • 好文档不仅可以可以记录代码的实现思路和原理,同时也能够更好的将核心思想传递给接手的人,更好的维护。
  • 基本上优秀的开源库都要非常详尽的说明文档,比例 Alamofire,QMUI 都很值得学习。

知识点问题梳理

这里罗列了四个问题用来考察你是否已经掌握了这篇文章,如果没有建议你加入收藏再次阅读:

  1. Objective-C 和 Swift 代码注释的区别?

  2. 对包含多个参数的方法注释与单个参数对方法有什么区别?

  3. 代码注释都支持哪些 Markdown 格式?

  4. Jazzy 支持几种模式的文档生成?