[关闭]
@Rookie 2018-05-09T10:49:28.000000Z 字数 7060 阅读 2074

OC 代码规范

iOS开发


Objective-C 编码规范

目录

目录结构

项目目录

  1. ———Vendors //不使用CocoaPod时第三方类库
  2. ———Models
  3. ———ViewModels //MVVM设计模式时需要使用
  4. ———Views
  5. ———Controllers
  6. ———Networks
  7. ———Utils
  8. ———Category
  9. ———Helpers
  10. ———Tools
  11. ———Resources
  12. ———Documents
  13. ———Images
  14. ———Audios
  15. ———Data
  16. ———Mscros

资源目录

命名

基本原则

  1. extern ushort APIDefaultPageSize; // 还行,能明白意思了
  2. extern ushort APIDefaultFetchPageSize; // 加上些限定更好一些
  3. extern ushort APIFetchPageSizeDefault; // 再好些,把重要的往前放
  4. YHToolbarComment // 不推荐
  5. YHCommentToolbar // OK,把类型(toolbar)置后

命名空间

视图命名

为了举例,我们假定有 UserTagCategory 这几种 model 类型。

对象展示一般分列表和单个详情,其 view controller 分别使用 **Model**ListController 和 **Model**DetailController,推荐的语素顺序是:Model名 + 限定与修饰 + ListController|DetailController。举例说明:

  1. // OK
  2. TagUserUsedListController
  3. TagInCategoryListController
  4. CategoryDetailController
  5. // 不推荐,列表统一使用 ListController,不指明是 table view 还是 collection view
  6. UserFollowerTableViewController
  7. // OK
  8. UserFollowerListController
  9. // 不推荐
  10. UserLikedTagListController
  11. // OK,把显示的对象放在第一位
  12. TagUserLikedListController
  13. // 糟糕,如果是 view controller,必须以 Controller 或 Displayer 结尾
  14. TagListView

经常为了便于多个界面复用,我们会把 model 的显示统一在一个 view controller 中,在其他界面嵌入这个 view controller。我们把这类专门管理显示的 view controller 叫做 displayer。如:

  1. UserListDisplayer
  2. TagListDisplayer

UIView 级别的组件不要以 Controller 或 Displayer 结尾,如果起到管理作用可以使用 control 结尾。

动机

把 model 名放在首位(如 TagUserLikedListController 而不是 UserLikedTagListController)的主要考量是便于搜索。因为 Xcode 不支持乱序搜索,关键词只能从前往后才会有结果。

如果限定词在前,因为不同人理解差异,自己也会遗忘,这个限定词经常是输入不能的,只能搜 TagList 再从列表中查找,等于第一位的查找语素就废掉了。当 model 类型在第一位时,基本上熟悉这个项目的人都清楚要查找的视图显示的是什么类型,第一位正确了,后面添加/修改限定就很方便了。

另一个便利的场景是参考之前界面实现另一个界面时,查找的大都是相同类型的界面,如实现 UserFollowerListController 参考 UserFollowingListController;而相同限定的场景比较少见,像 UserLikedTagListController 参考 UserLikedCategoryListController 的可能性就较少。

PS: 务必经常使用 Xcode 的 Open Quickly(默认快捷键 Command+Shift+O)

方法名

例:

  1. Objective-C
  2. // OK
  3. - (NSString *)name;
  4. // 糟糕,应用上面的写法
  5. - (NSString *)getName;
  6. // OK,但极少使用
  7. - (void)getName:(NSString **)buffer range:(NSRange)inRange;
  8. // OK
  9. - (NSSize)cellSize;
  10. // 糟糕,应用上面的写法
  11. - (NSSize)calcCellSize;
  12. // 对 controller 做一般设置,OK
  13. - (void)setupController;
  14. // 列出具体设置的内容,更好
  15. - (void)setupControllerObservers;
  16. // 糟糕,set 专用于设置属性
  17. - (void)setController;
  1. Objective-C
  2. // 来自官方文档
  3. insertObject:atIndex: // OK
  4. insert:at: // 不清晰,插入了什么?at 具体指哪里?
  5. removeObjectAtIndex: // OK
  6. removeObject: // OK
  7. remove: // 糟糕,什么被移除了?

协议名

好的协议名应能立刻让人分辨出这不是一个类名,除了以常用的 delegate、dateSource 做结尾外,还可以使用 …ing 这种形式,如:NSCodingNSCopyingNSLocking

通知命名

基本命名格式是:[与通知相关的类名] + [Did | Will] + [UniquePartOfName] + Notification,例:

  1. Objective-C
  2. NSApplicationDidBecomeActiveNotification
  3. NSWindowDidMiniaturizeNotification
  4. NSTextViewDidChangeSelectionNotification
  5. NSColorPanelColorDidChangeNotification

临时变量命名

  1. // OK
  2. wCell, vcMaster, vToolbar
  3. // 糟糕,数据类型作为前缀
  4. bool_switchState, floatBoxHeight

推荐的前缀:

前缀 含义
ix 序号,起始为0
in 序号(自然数范围),起始为1
if 类型为浮点的“序号”
x 坐标
y 坐标
w 宽度
h 高度
vc 视图控制器
v 视图

常量命名

除以上规则约定外,其他常量约定了以下前缀:

前缀 含义
k 宏常量
CDEN Core Data entity name
UDk User Default key
KCk Key Chain key
APIURL 接口地址

另见:常量管理

资源命名

图片资源在放入xcassets中相应视图控制器文件夹的基础上,根据[相应的功能] + [Btn | BtnClick | Icon],例:

  1. UserAvatarDefaultIcon
  2. PostCommentBtn
  3. ClickShowWebsiteBtn

大小写

缩写

可以使用广泛使用的缩写,如 URLJSON,并且缩写要大写。但像将download简写为dl这种是不可以的。

  1. Objective-C
  2. // OK
  3. ID, URL, JSON, WWW
  4. // 糟糕
  5. id, Url, json, www
  6. destinationSelection // OK
  7. destSel // 糟糕
  8. setBackgroundColor: // OK
  9. setBkgdColor: // 糟糕

其他

i,j专用于循环标号

为私有方法命名不要直接以“”开头,而应以“命名空间”开头。

代码格式化

空格

类方法声明在方法类型与返回类型之间要有空格。

  1. Objective-C
  2. // 糟糕
  3. -(void)methodName:(NSString *)string;
  4. // OK
  5. - (void)methodName:(NSString *)string;

条件判断的括号内侧不应有空格。

  1. // 糟糕
  2. if ( a < b ) {
  3. // something
  4. }
  5. // OK
  6. if (a < b) {
  7. // something
  8. }

关系运算符(如 >=!=)和逻辑运算符(如 &&||)两边要有空格。

  1. // OK
  2. (someValue > 100)? YES : NO
  3. // OK
  4. (items)?: @[]

二元算数运算符两侧是否加空格不确定,根据情况自己定。一元运算符与操作数之前没有空格。

多个参数逗号后留一个空格(这也符合正常的西文语法)。

花括号

方法的花括号推荐另起一行。方法内部需要写在一行。

  1. - (void)methodName:(NSString *)string {
  2. ↑空格 ↑空格,推荐花括号在一行
  3. if () {
  4. 空格↑ ↑空格,花括号不要另起一行
  5. }
  6. else {
  7. 要换行↑ ↑空格,花括号不要另起一行
  8. }
  9. }

动机

Xcode 默认的花括号位置是这样的:方法内部的各种补全都是在同一行的;方法定义的比较混乱,默认模版另起一行,但从 Interface Builder 中连线生成的方法在同一行的。

考虑到 Xcode 的默认行为,方法内部要另起一行,方法所在行不强制定死。另外,模版可以定制,而 IB 生成的代码不可定制,所以不另起一行的写法优先。

另起一行的写法在代码折叠后非常难看。

空行

相对独立的程序块之间、变量说明之后必须加空行。

折行

与多数其他规范不同,不建议手动折行。

动机

手动折行的效果严重宽度依赖于窗口宽度——窗口过宽浪费宝贵的屏幕空间,较窄时可能无法阅读。而且 Xcode 自动折行的效果还是不错的。

代码组织

尽早返回错误:

  1. // 为了简化示例,没有错误处理,并使用了伪代码
  2. // 糟糕的例子
  3. - (Task *)creatTaskWithPath:(NSString *)path {
  4. Task *aTask;
  5. if ([path isURL]) {
  6. if ([fileManager isWritableFileAtPath:path]) {
  7. if (![taskManager hasTaskWithPath:path]) {
  8. aTask = [[Task alloc] initWithPath:path];
  9. }
  10. else {
  11. return nil;
  12. }
  13. }
  14. else {
  15. return nil;
  16. }
  17. }
  18. else {
  19. return nil;
  20. }
  21. return aTask;
  22. }
  23. // 改写的例子
  24. - (Task *)creatTaskWithPath:(NSString *)path {
  25. if (![path isURL]) {
  26. return nil;
  27. }
  28. if (![fileManager isWritableFileAtPath:path]) {
  29. return nil;
  30. }
  31. if ([taskManager hasTaskWithPath:path]) {
  32. return nil;
  33. }
  34. Task *aTask = [[Task alloc] initWithPath:path];
  35. return aTask;
  36. }

禁止在类的 interface 中定义任何 iVar 成员,只允许使用属性,但可以在特定情形中使用属性生成的 iVar。

尽量总是使用点操作符访问属性,而不是属性生成的 iVar 变量。以下情形除外:

动机

如果使用 iVar,很多情况要特殊处理,容易出错。总是使用成员,规则简单,不易出问题。

直接访问 iVar 的 block 会 retain iVar 所属的对象,这点很容易被忽略

定义和使用 iVar 都会产生编译警告,只不过默认设置没启用这两个警告

Property attributes

什么时候使用 copy?

相关 Demo 可在 https://github.com/BB9z/PropertyTest 获得。

常量

除非调试用的、控制不同编译模式行为的常量可用宏外,其他常量不得用宏定义。

常量定义示例:

  1. // 头文件
  2. extern ushort APIFetchPageSizeDefault; // 无const,可在外部修改
  3. // 实现文件
  4. ushort APIFetchPageSizeDefault = 10;

注释

使用Xcode插件VVDocumenter-Xcode可以有效的进行编写注释的需求
所有的.h文件中对外的接口方法定义中必须进行注释,而.m文件中除非已经在.h中已经注释的方法或者是get/set方法可以不注释外,其余函数必须进行注释。
修改代码同时修改相应的注释,以保证注释与代码的一致性。不再有用的注释要删除。
最后一点,尽量让代码可以自表述,而不是依赖注释。

注释应该表达那些代码没有表达以及无法表达的东西。如果一段注释被用于解释一些本应该由这段代码自己表达的东西,我们就应该将这段注释看成一个改变代码结构或编码惯例直至代码可以自我表达的信号。我们重命名那些糟糕的方法和类名,而不是去修补。我们选择将长函数中的一些代码段抽取出来形成一些小函数,这些小函数的名字可以表述原代码段的意图,而不是对这些代码段进行注释。尽可能的通过代码进行表达。你通过代码所能表达的和你想要表达的所有事情之间的差额将为注释提供了一个合理的候选使用场合。对那些代码无法表达的东西进行注释,而不要仅简单地注释那些代码没有表达的东西。”[^2]

块注释

方法内部禁止使用块注释。除非要临时注释大段代码,一般情况总应使用行注释。

动机

因为块注释不能正确嵌套。

其他

异常

参考

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注