前言
在一个应用的整个开发过程中涉及到了无数的步骤。其中一些是应用的说明 , 图片的创作 , 应用的实现 , 和实现过后的测试 . 写代码可能组成了这个过程的绝大部分 , 因为正是它给了应用生命 , 但是这样还不够 , 与它同等重要的还有代码的注释和文档编写 . 不管代码写的有多好 , 如果缺少了对应的好的注释文档 , 很有可能在将来带来麻烦 . 不幸的是 , 许多开发者都忽视或忽略了代码文档的重要性 , 而这非常糟糕 , 因为好的程序不仅仅是好的代码 . 它需要更多的东西.
对于注释的重要性 这里我不再过多阐述 , 有兴趣的话就看看下面引用的一段对注释重要性的解释:
谈到编写注释文档,显然我不是说仅仅简单的在实现文档里添加几行注释。肯定是更多的东西。但是首先,为什么对代码进行注释这么重要?好吧,这里有两个情景:不管你是单独工作,或者你是团队的一份子。让我们来分别解释一下。
如果你是一个正在开发的应用的唯一开发者,有理由相信写注释文档会消耗时间,所以忽略不做会让你更迅速完成目标。除此之外,很容易说服自己既然都是独立开发者了没有太大必要来做注释文档。但是相信我,这将会是在应用开发期间做的最坏的决定。假设你成功的开发完成了应用,无论是在app store上销售或者卖给你的客户,然后你把它保存起来。六个月后,你必须要通过添加几个新功能来创建一个这个应用的新版本。当你重新打开这个工程然后查看已有的代码,在你写下第一行新代码之前一段时间,你会意识到一个残酷的真相:你几乎不记得任何东西!你做过什么,你怎么做的,为什么要这么做,都会很难回想起来!你必须通过一个痛苦的方法来在脑袋里重新唤起这个工程,这个方法就是从工程的最开始,一行行的“解密”你的实现代码。仅仅是几行注释根本没什么帮助,最终直到你理解所有东西之前会用很长的时间且花掉许多精力。正在读这些内容的很大一部分的你们都可能遇到过那种情况,而且我很肯定的给你说过去我也遇到过。这种情况真的是噩梦一样,那时你经常想干脆从头开始做一个工程算了。当然,这里提到的场景可能仅仅会是一个...场景,只要我们花一点点时间来编写合适的注释文档。
另一方面,如果你是团队里的一员来开发工程,那么避开给代码写注释文档将会是灾难性的。当你给其他开发者分享代码时,你必须解释你的观点(在代码里),当时做了什么,是怎样做的,当然其他的开发者也被要求做同样的事情。没有一个情形是大项目的开发者们对后面开发者们开发的代码都有全面的了解,因为若是这样,将会浪费掉很多不必要的时间。因此,在这种情况下的编写注释代码一方面就像是做交流,另一方面也是帮助团队里的其它开发者了解你的代码的含义。毕竟,每个程序员编写代码的风格都跟其他人不一样,所以表达清楚你的代码功能是件必须做的任务。
概述
在开始之前 , 让我说明两件事情 . 第一,我不是试图让你偏执的写文档 , 而是说服你编写合适的注释将会提高你的编程生涯 . 第二 , 编写代码注释文档是你必须接受的习惯 , 而绝对不是浪费时间 .
注释的使用说明
正如你知道的,在Objective-C 和 Swift中写一条注释的最简单办法是用两条斜杠:
// 我是一条注释.
你可以 (且必须) 像上面那样来放置你的注释 , 以便分清每个部分 . 但是 , 当谈到代码注释文档 , 我肯定不是指的上面的注释 . 如果整个教程都专注于此肯定毫无意义 . 注释文档意味着以结构化的方法使用特殊的关键字 , 也叫标签来写注释 , 使用特殊的符号来标示注释区域 , 因此编译器可以完美的理解这个过程 . 只有一些简单的规则需要遵守 . 上面操作的所有结果就是你的注释文档可以在三个不同的地方展示:
- 在Utilities面板的Quick Help Inspector 里.
- 当你按下Option键然后点击方法 , 类或属性名时弹出的帮助菜单 Help Popup 里.
- 在代码实现弹出框里.
除此之外 , 合适的代码注释让你可以使用众多的工具来为你的应用创建完整的HTML文档 , 例如the HeaderDoc 和 Doxygen . 它们两个后面会提到.
当使用 Objective-C 写代码时有三种可能的方法来标示一个注释文档区域:
把你的注释包含在/** 你的注释 */ 块里。
把你的注释包含在 /*! 你的注释 */块里。
以三条斜杠 ///开始的注释行
在这个教程实例中我们将会用第二种方法来写我们的注释文档 . 我选择它出于两个原因 : 第一 , 它是唯一一个能被 HeaderDoc 识别的格式 , 而且如果注释块不是以它开头 , 帮助页也不会被生成 . 第二 , 尽管 Doxygen 更倾向于第一种格式 , 它也能识别第二种 . 因此 , 第二种格式将会在两种方法下都适用 . 第三种格式通常在注释一行时用到 , 例如属性值时 , 因此 , 我们还是坚持用第二种格式.
现在 , 当写注释文档时 , 你可以使用特定的关键值 (或标签) . 标签被分为两个大类 : 第一个是 top level 标签 , 它可以用来指定哪种类型的代码被注释了 , 例如类 , 结构体 , 文件 , 等等 . 注意top level标签不是必须使用,但是肯定会帮助导出工具 (例如 HeaderDoc 和 Doxygen) 创建出更好的结果 . 第二个是second level标签 , 它指定了每个注释文档块的细节 . 这个类型的标签正是你需要的 , 因为每一个都定义了另外的注释文档部分.
下面我给出了最重要的second level标签 , 但是注意了这并不是全部 . 我们稍后会看到一些 top level标签 . 我这里列出来的是最常用到的:
- @brief: 使用它来写一段你正在文档化的method, property , class , file , struct , 或enum的短描述信息.
- @discussion: 用它来写一段详尽的描述 . 如果需要你可以添加换行 .
- @param: 通过它你可以描述一个 method 或 function的参数信息 . 你可以使用多个这种标签.
- @return: 用它来制定一个 method 或 function的返回值.
- @see: 用它来指明其他相关的 method 或 function . 你可以使用多个这种标签.
- @sa: 同前一条类似.
- @code: 使用这个标签 , 你可以在文档当中嵌入代码段 . 当在Help Inspector当中查看文档时 , 代码通过在一个特别的盒子中用一种不同的字体来展示 . 始终记住在写的代码结尾处使用@endcode标签.
- @remark:在写文档时,用它来强调任何关于代码的特殊之处.
你可以在 这里 (HeaderDoc User Guide)找到包含所有支持的标签的列表.
注意 : @符号是每个标签的前缀 . 同样 , 你也可以在文本中使用特殊字符switches , 这样就可以改变它的类型和格式。例如,Text 以会让 Text单词成为黑体,同时Text也会让 Text 单词的类型为italic. 有趣的是你也可以把部分文本以代码形式展现(不是代码段),如果写下@cText,当帮助文档在Xcode上展现时,它会导致展示一个不同的字体格式。
除开上面说的 , 你也可以替换@符号为反斜杠(\) . 那样的话标签就会像这样被展示: \brief, \param, \return,等等 . 注意反斜杠最常在 Doxyten 文档系统里面被使用 , 而@符号常在 HeaderDoc 里面被使用 . 在这里我们会在所有地方使用@ , 因为它在两个系统中都通用.
注释的使用演示
属性:
让我们看看以上我提到的内容是怎样使用的 . 首先我声明一个属性:
@property (nonatomic , copy ) NSString *name;
然后如下面所示添加注释文档:
/*! @brief 用户姓名属性 */
@property (nonatomic , copy ) NSString *name;
然后到这个类某一个方法中 , 开始输入这个属性 . 你将看到在代码填充弹出框里我们刚刚写下的注释就在那里了.
而且不仅这样 . 当在键盘上按住 Option 键,点击name属性就会让帮助窗口弹出:
更多的 , 如果在Utilities面板打开 Help Inspector , 你会在那里找到相同的文档.
注意在上面的注释当中 @brief 标签可以被去掉而不会导致任何问题 . 意味着下面的这条注释也是有效的:
/*! 用户姓名属性 */
@property (nonatomic , copy ) NSString *name;
同样的,下面的也一样:
/** 用户姓名属性 */
@property (nonatomic , copy ) NSString *name;
而且下面的这条也一样:
/// 用户姓名属性 */
@property (nonatomic , copy ) NSString *name;
方法:
我来演示一下方法注释的示例 , 那么只是公有方法(在头文件里的)的文档是可见的 . 无论你在类的私有部分中写的任何文档在 Xcode 帮助文档里都是可见的 , 但是任何实现部分都没有被导出到注释文档里 . 所以 , 记住这些 , 现在让我们在LaunchViewController.h 文件里定义一个公有方法:
- (NSString *)stringToMD5:(NSString *)fromString;
很显然 , 这个方法将会把一个字符串转换为MD5加密的 . 现在我们来添加注释文档:
/*!
* @brief MD5加密字符串
*
* @discussion 传入一个字符串对象 并通过MD5加密算法对其进行 16位加密
*
* @param fromString为要加密的字符串对象
*
* @return 16位小写加密字符串
*/
- (NSString *)stringToMD5:(NSString *)fromString;
注意在上面我们使用HTML开关来让所有包含的文字分别是粗体和斜体。同样注意我们是怎样使用@c开关来标识内嵌代码
接下来我们这在ViewController.m文件中实现这个方法:
#pragma mark - MD5加密字符串
- (NSString *)stringToMD5:(NSString *)fromString{
const char *cStr = [fromString UTF8String];
unsigned char result[CC_MD5_DIGEST_LENGTH];
CC_MD5(cStr, (CC_LONG)strlen(cStr), result);
return [[NSString stringWithFormat:@"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
result[0], result[1], result[2], result[3],
result[4], result[5], result[6], result[7],
result[8], result[9], result[10], result[11],
result[12], result[13], result[14], result[15]
] lowercaseString];
}
同样我们把光标放到方法名上,在Quick Help Inspector里查看:
方法中调用这个函数 , 那么就可以在函数自动填充窗口里看到简介描述:
很棒 , 是不是 ? 可以想象你的代码那样文档化后会变得多有帮助 且能自我解释清楚代码意义 , 特别是当你同其他团队人员一起工作时.
下面我们玩点有意思的 , 为这个注释增加点更直观的效果:
没错 代码框 和 备注 , 通过@code - @endcode
标签 和 @remark
标签 可以让我们的自己的文档注释和Xcode默认文档比起来毫不逊色.
在前面部分我们浏览了写属性或方法时需要遵守的记录文档规则 . 我故意从属性和方法开始介绍编写文档 , 因为你的大部分开发时间都是消耗在那里。现在我们已经看到了最主要的部分 , 现在继续看看怎样对 files , classes , structs 和 enums 添加注释.
让我们从files开始 , 怎样在Objective-C中写下能提供信息的文档 . 当同其他人分享编程工作时 , 或者当你以开放源代码组件形式分发代码时 , 那么添加file documentation就是必须的 , 因为这是最佳的地方来向你的开发伙伴或者使用你代码的人提供明确的信息 . 通常 , 你将要更关注header 文件 (.h) 以及在它里面的描述信息 , 因为这些会保留在将被导出的最终文档里 (并不在Xcode里) ; 但是 , 这并不代表你不需要在implementation文件中添加描述信息 . 不要忘记当在Xcode中打开工程时 , 所有的东西都在那儿 , 不仅仅是header 文件 , 因此保证你不会留下没被记录文档的部分 . 除此之外 , 实现部分不会在导出文档中看到 , 但是所有文件的描述信息始终都是可见的.
文件:
让我介绍一些当你在记录一个文件时会用到的新标签:
- @file: 使用这个标签来指出你正在记录一个文件 (header 文件或不是) . 如果你将使用Doxygen来输出文档 , 那么你最好在这个标签后面紧接着写上文件名字 . 它是一个top level 标签.
- @header: 跟上面的类似 , 但是是在 HeaderDoc中使用 . 当你不使用 Doxygen时 , 不要使用上面的标签.
- @author: 用它来写下这个文件的创建者信息.
- @copyright: 添加版权信息.
- @version: 用它来写下这个文件的当前版本 , 如果在工程生命周期中版本信息有影响时这会很重要.
当然你还可以使用更多的标签 , 但是这些都是最常使用的一部分 . 我建议你通览HeaderDoc 或 the Doxygen文档 , 这样就可以发现一些额外的你想使用的关键字.
现在我们来看对LaunchViewController.h 头文件添加注释 . 找到文件的开头 , 就在import 命令之前 . 在那里添加下面的几行:
/*!
* @header LaunchViewController.h
*
* @brief 启动视图控制器,在程序启动时加载显示
*
* @author LEE
* @copyright © 2016年 lee. All rights reserved.
* @version 16.3.3
*/
你可以把 LEE 替换成你自己的名字或者公司的名字 . 同样的 , 使用 brief标签而不省略它是很好的习惯 , 因为它会让文档系统 (在HeaderDoc 和 Doxygen中稍后将会看到) 在输出的HTML网页上展示你在这儿添加的简短描述 . 我再提醒一次 , 在Doxygen里你可以使用反斜杠来代替“@”。同样的 , 在这里你将不会看到刚才说的文档长什么样 , 但是我们将会在后续输出HTML文件时展示.
以上的都很棒 , 但是实事却是在大多数情况下 , 你创建的新文件里由Xcode自动添加的提供信息的默认注释都非常好而且够用了 . 当你在团队里同其他人一起协作 , 并且每个成员必须描述清楚他负责的那些文件的细节信息时 , 或者当你打算使用 HeaderDoc 或 Doxygen来输出一个工程的完整文档时 , 又或者当你是独立开发者但是工程拥有数量众多的文件时 , 你可能想创建一个文件描述块 . 不管怎样 , 由你自己决定你的文档系统需要完善到哪个level.
类:
让我们来看看怎样对class或者protocol写注释文档 . 再一次的 , 我只给出最常用的标签 . 自己查看说明文档了解更多标签信息.
- @class: 用它来指定一个class的注释文档块的开头 . 它是一个top level标签 . 在它后面应该给出class名字.
- @interface: 同上.
- @protocol: 同上两个一样 , 只是针对protocols.
- @superclass: 当前class的superclass.
- @classdesign: 用这个标签来指出你为当前class使用的任何特殊设计模式 (例如 , 你可以提到这个class是不是单例模式或者类似其它的模式).
- @coclass: 与当前class合作的另外一个class的名字.
- @helps: 当前class帮助的class的名字.
- @helper: 帮助当前class的class名字.
实际上 , 可能除了superclass你几乎很少用到上面的标签 . 可用标签的列表很长 , 只是我觉得在这里列出更多没太大意义 . 必须指出的是从superclass开始以及下面的所有标签 , 都不能被 Doxygen识别 , 只能被HeaderDoc识别 . 同样的 , 在Xcode里的 Quick Help 和 Help Popup 弹出框里展示的是各个标签旁边的值 , 而不会展示标签本身 . 因此 , 用不用他们取决于你是否使用文档系统以及使用哪一个.
/*!
* @class LaunchViewController
*
* @brief 启动视图控制器类
*
* @superclass SuperClass: UIViewController
* @classdesign 视图控制器基类
* @coclass AppDelegate
* @helps 不帮助其他类
* @helper 无帮助类
*/
@interface LaunchViewController : UIViewController
协议:
按照上面的方法也可以对protocol添加注释 . 在LaunchViewController.h 文件开头添加下面的几行:
/*!
* @protocol ViewControllerDelegate
*
* @brief 启动视图控制器协议
*/
@protocol LaunchViewControllerDelegate
/*!
* 启动视图控制器已经消失
*/
-(void)launchViewControllerDidDisappear;
可以看到在上面 , 我们也声明了一个delegate 方法 . 可能此刻你觉得那毫无意义 , 但是我故意把它放在这里是作为示例demo ; 当我们使用 HeaderDoc 和 Doxygen 来导出文档时就会在导出的页面看到它了.
结构体:
现在我们讨论了files,classes 和protocols 希望你有了一个大概的了解,现在看看一些特殊示例:怎样对structs 和 enumerations进行注释。它们的共同点是在两个当中都使用 @typedef top level 标签,以此来标示注释块的开始(再次提醒使用top level 完全自愿)。
特别是对于Doxygen系统,不使用@typedef标签,你应该对structs使用@struct 标签,对enums使用@enum标签。
让我们看以下的代码。把它添加到LaunchViewController.h 文件的interface之前:
typedef struct {
int type;
float time;
BOOL isShowImage;
} LaunchConfig;
现在把下面的注释添加在它前面:
/*!
* @typedef LaunchConfig
*
* @brief 启动设置.
*
* @discussion 启动视图控制器会根据此结构体内的变量值实现相应的操作.
*
* @field launchType 启动类型
* @field launchTime 启动视图控制器显示时间
* @field isShowImage 是否显示图片
*/
typedef struct {
int launchType;
float launchTime;
BOOL isShowImage;
} LaunchConfig;
可以看到 , 出现了一个名叫 @field 的新标签 . 这个标签的目的是对struct里面的每一个变量进行描述 . 再次 , 我必须强调 HeaderDoc 和 Doxygen 的不同之处 . 在 HeaderDoc 里 , 这个标签是可接受且有效的 . 但是 , 在 Doxygen 里却不是这样 . 在这个案例里我们做的仅仅是分开对每一个变量进行注释 . 这样做会使当我们在 implementation 文件里使用它们时能展示出每个的注释 . 我们来看看吧 (注意标签和变量上方的注释有着不同的值 , 所以在后面会更轻松的区别出来):
/*!
* @struct LaunchConfig
*
* @brief 启动设置.
*
* @discussion 启动视图控制器会根据此结构体内的变量值实现相应的操作.
*
* @field launchType 启动类型
* @field launchTime 启动视图控制器显示时间
* @field isShowImage 是否显示图片
*/
typedef struct {
/*! 启动类型 */
int launchType;
/*! 启动视图控制器显示时间 */
float launchTime;
/*! 是否显示图片 */
BOOL isShowImage;
} LaunchConfig;
如果现在到 LaunchViewController.m 文件中定义一个这个struct的变量 , 无论输入以上任何变量的一个都会在弹出窗口里看到它的描述.
Enumerations也是同样的 , 所以我把它作为练习留给你自己去创建一个enum类型然后进行注释 . 非常简单 , 是不是 ? 当enum创建好后 , 到viewDidLoad方法里去查看它的注释会不会在Xcode的帮助选项里展示出来.
错误提示
在先前的例子里可以看到 , 在 @param 标签旁必须写出变量名字 , 之后才是描述信息 . 当然 , 误输入一个变量名 , 特别是当它有些复杂时 , 以及创建的文档包含错误都是很有可能的 . 但是 , 有个方法可以避免这种情况发生 , 我猜你可能并不知道Xcode可以在这时成为你的可靠助手.
尽管Xcode在文档开始写时就处理了 , 它也可以帮助你避免误输入任何变量名 . 只要开启一个配置就能让它做到这点 . 让我们来看看我到底想表达的是什么:
在 Project Navigator里 , 点击工程 , 找到Build Settings标签 , 在搜索框里输入comments关键字然后等待下方给出的结果 . 其中一个结果就是名为Documentation Comments的配置 , 通常它的默认值是NO . 你做的仅仅是把它的值设为YES , 一切就绪 . 如下图所示:
设置好后 , 我们举个例子来演示一下效果.
就拿我们之前的写好的示例代码来说 , 我将注释中 @param 标签后面的参数名故意写错 , 大家可以看到 Xcode为我们弹出了警告:
这时候你点击黄色的警告三角 , 就会看到Xcode为你自动推荐了正确的变量名 , 你可以继续点击它就会帮你自动修复 , 当然你也可以自己去修改.
有了上面的这些 , 麻麻再也不需要担心在写文档时误输入任何变量名了.
总结
上面说了这么多相信你已经对文档注释有了更多的了解 , 想象一下 当你在使用别人的库做开发时 , 调用某些方法 你可能并不知道这个方法是做什么的 , 但是当代码填充框内的注释出现时 , 是不是心里暗爽呢 ? 因为你不需要再花额外的时间去做了解 , 换位思考 , 当别人在使用你的代码时 , 也可以获得相同的便利 , 岂不是两全其美吗 ?
之前我们有提到两个工具 Doxygen 和 HeaderDoc , 其中有一些注释标签的使用也是根据这两个做了很多说明 . 如果你想继续了解注释文档的生成 , 那么请继续阅读__注释文档 __, 在这里你会了解到如何使用不同工具来生成属于自己的注释文档.
我是LEE , 如果你还有更好的建议 欢迎给我留言 , 如果喜欢记得点赞哟 ! 么了个哒~
注: 本篇文章主要参考自AppCoda.