目录
核心原则
命名
- 文件命名
- 视图命名
- 方法命名
- 变量命名
- 图片命名
代码格式
- 空格
- 函数的书写
- 函数调用
- 协议(Protocols)
- 闭包(Blocks)
- 代码组织
- Property attributes
编码风格
- 不要使用new方法
- Public API要尽量简洁
- 引用框架的根头文件
- BOOL的使用
- init和dealloc
- 单例
- Pragma Mark
注释要求
- 块注释
其他
- 代码文件组织
- 资源文件组织
- 注释要求
- 忽略没有使用变量的编译警告
- 手动标明警告和错误
核心原则
原则一:代码应该简洁易懂,逻辑清晰
因为软件是需要人来维护的。这个人在未来很可能不是你。所以首先是为人编写程序,其次才是计算机:
- 不要过分追求技巧,降低程序的可读性。
- 简洁的代码可以让bug无处藏身。要写出明显没有bug的代码,而不是没有明显bug的代码。
原则二:面向变化编程,而不是面向需求编程。
需求是暂时的,只有变化才是永恒的。
本次迭代不能仅仅为了当前的需求,写出扩展性强,易修改的程序才是负责任的做法,对自己负责,对公司负责。
原则三:先保证程序的正确性,防止过度工程
过度工程(over-engineering):在正确可用的代码写出之前就过度地考虑扩展,重用的问题,使得工程过度复杂。
- 先把眼前的问题解决掉,解决好,再考虑将来的扩展问题。
- 先写出可用的代码,反复推敲,再考虑是否需要重用的问题。
- 先写出可用,简单,明显没有bug的代码,再考虑测试的问题。
命名
基本原则:清晰
尽可能遵守 Apple 的命名约定,命名应该尽可能的清晰和简洁,但在Objective-C中,清晰比简洁更重要。
- 文件夹名、类名采用大驼峰(UpperCamelCase)
- 类成员、方法小驼峰(lowerCamelCase)
- 不使用下划线作为私有方法的前缀,此方式被苹果保留。
- C函数的命名用大驼峰。
- 避免使用newXXX、getXXX、setXXX来命名变量和方法。
- 整个工程的命名风格要保持一致性,不同类中完成相似功能的方法应该叫一样的名字。比如我们总是用count来返回集合的个数,不能在A类中使用count而在B类中使用getNumber。
// ******推荐****** //
insertObject:atIndex:
removeObjectAtIndex:
// ******反对****** //
//insert的对象类型和at的位置属性没有说明
insert:at:
//remove的对象类型没有说明,参数的作用没有说明
remove:
不要使用单词的简写,拼写出完整的单词。
可以使用广泛使用的缩写,如 URL
、JSON
,并且缩写要大写。但像将download
简写为dl
这种是不可以的。
// ******推荐****** //
ID, URL, JSON, WWW
destinationSelection
setBackgroundColor:
// ******反对****** //
id, Url, json, www
//不要使用简写
destSel
setBkgdColor:
文件命名
项目名前缀+文件名+类型后缀。
例如:RXFHomeView、RXFHomeViewController、RXFHomeModel
视图命名
一般视图有几种,列表(List)、详情(Detail)、添加(Add)、编辑(Edit)、混合视图(视功能以及对应模块来定)。规范:Model+限定与修饰+ViewController
。举例说明:
// 推荐
TagUserUsedListController
TagInCategoryListController
CategoryDetailController
// 不推荐,列表统一使用 ListController,不指明是 table view 还是 collection view
UserFollowerTableViewController
// 推荐
UserFollowerListController
// 不推荐
UserLikedTagListController
// 推荐,把显示的对象放在第一位
TagUserLikedListController
// 不推荐,如果是 view controller,必须以 Controller 或 Displayer 结尾
TagListView
经常为了便于多个界面复用,我们会把 model 的显示统一在一个 view controller 中,在其他界面嵌入这个 view controller。我们把这类专门管理显示的 view controller 叫做 displayer
。如:
UserListDisplayer
TagListDisplayer
UIView 级别的组件不要以 Controller 或 Displayer 结尾,如果起到管理作用可以使用 control 结尾。
方法命名
- Objective-C的方法名通常都比较长,这是为了让程序有更好地可读性,按苹果的说法“好的方法名应当可以以一个句子的形式朗读出来”。
- 方法一般以小写字母打头,每一个后续的单词首字母大写,方法名中不应该有标点符号(包括下划线)。
- 如果方法表示让对象执行一个动作,使用动词打头来命名,注意不要使用
do
,does
这种多余的关键字,动词本身的暗示就足够了:
//动词打头的方法表示让对象执行一个动作
- (void)invokeWithTarget:(id)target;
- (void)selectTabViewItem:(NSTabViewItem *)tabViewItem;
- 对于有多个参数的方法,务必在每一个参数前都添加关键词,关键词应当清晰说明参数的作用:
// ******推荐****** //
//保证每个参数都有关键词修饰
- (void)sendAction:(SEL)aSelector toObject:(id)anObject forAllCells:(BOOL)flag;
// ******反对****** //
//错误,遗漏关键词
- (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag;
不要用and
来连接两个参数,通常and
用来表示方法执行了两个相对独立的操作(从设计上来说,这时候应该拆分成两个独立的方法):
// ******推荐****** //
//使用"and"来表示两个相对独立的操作
- (BOOL)openFile:(NSString *)fullPath withApplication:(NSString *)appName andDeactivate:(BOOL)flag;
// ******反对****** //
//不要使用"and"来连接参数
- (int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes;
方法的参数命名也有一些需要注意的地方:
- 和方法名类似,参数的第一个字母小写,后面的每一个单词首字母大写
- 不要在方法名中使用类似
pointer
,ptr
这样的字眼去表示指针,参数本身的类型足以说明 - 不要使用只有一两个字母的参数名
- 不要使用简写,拼出完整的单词
变量命名
1.变量的名称必须同时包含功能与类型
UIButton *addBtn //添加按钮
UILabel *nameLbl //名字标签
NSString *addressStr//地址字符串
2.系统常用类作实例变量声明时加入后缀
类型 | 后缀 |
---|---|
UIViewController | VC |
UIView | View |
UILabel | Lbl |
UIButton | Btn |
UIImage | Img |
UIImageView | ImgView |
NSArray | Array |
NSMutableArray | MArray |
NSDictionary | Dict |
NSMutableDictionary | MDict |
NSString | Str |
NSMutableString | MStr |
图片命名
1.用英文命名,不用拼音
2.每一部分用分隔线'-'分隔
3.尽量表现内容+使用类型
4.尽量同一页面放置在同一个文件夹下
推荐:
login-nextstepbtn-highlighted.png
login-nextstepbtn-normal.png
代码格式
空格
类方法声明在方法类型与返回类型之间要有空格。
// 推荐
- (void)methodName:(NSString *)string;
// 反对
-(void)methodName:(NSString *)string;
条件判断的括号内侧不应有空格。
// 推荐
if (a < b) {
// something
}
// 反对
if ( a < b ) {
// something
}
关系运算符(如 >=
、!=
)和逻辑运算符(如 &&
、||
)两边要有空格。
// OK
(someValue > 100)? YES : NO
// OK
(items)?: @[]
函数的书写
一个典型的Objective-C函数应该是这样的:
- (void)writeVideoFrameWithData:(NSData *)frameData timeStamp:(int)timeStamp {
...
}
在-
和(void)
之间应该有一个空格,第一个大括号{
的位置在函数所在行的末尾,同样应该有一个空格。(我司的C语言规范要求是第一个大括号单独占一行,但考虑到OC较长的函数名和苹果SDK代码的风格,还是将大括号放在行末。)
如果一个函数有特别多的参数或者名称很长,应该将其按照:
来对齐分行显示:
-(id)initWithModel:(IPCModle)model
ConnectType:(IPCConnectType)connectType
Resolution:(IPCResolution)resolution
AuthName:(NSString *)authName
Password:(NSString *)password
MAC:(NSString *)mac
AzIp:(NSString *)az_ip
AzDns:(NSString *)az_dns
Token:(NSString *)token
Email:(NSString *)email
Delegate:(id)delegate;
函数调用
函数调用的格式和书写差不多,可以按照函数的长短来选择写在一行或者分成多行:
//写在一行
[myObject doFooWith:arg1 name:arg2 error:arg3];
//分行写,按照':'对齐
[myObject doFooWith:arg1
name:arg2
error:arg3];
//第一段名称过短的话后续可以进行缩进
[myObj short:arg1
longKeyword:arg2
evenLongerKeyword:arg3
error:arg4];
协议(Protocols)
在书写协议的时候注意用<>
括起来的协议和类型名之间是没有空格的,比如IPCConnectHandler()
,这个规则适用所有书写协议的地方,包括函数声明、类声明、实例变量等等:
@interface MyProtocoledClass : NSObject {
@private
id _delegate;
}
- (void)setDelegate:(id)aDelegate;
@end
一个类的Delegate对象通常还引用着类本身,这样很容易造成引用循环的问题,所以类的Delegate属性要设置为弱引用。
/** delegate */
@property (nonatomic, weak) id delegate;
闭包(Blocks)
根据block的长度,有不同的书写规则:
- 较短的block可以写在一行内。
- 如果分行显示的话,block的右括号
}
应该和调用block那行代码的第一个非空字符对齐。 - block内的代码采用4个空格的缩进。
- 如果block过于庞大,应该单独声明成一个变量来使用。
-
^
和(
之间,^
和{
之间都没有空格,参数列表的右括号)
和{
之间有一个空格。
//较短的block写在一行内
[operation setCompletionBlock:^{ [self onOperationDone]; }];
//分行书写的block,内部使用4空格缩进
[operation setCompletionBlock:^{
[self.delegate newDataAvailable];
}];
//在一个调用中使用多个block,注意到他们不是像函数那样通过':'对齐的,而是同时进行了4个空格的缩进
[myObject doSomethingWith:arg1
firstBlock:^(Foo *a) {
// ...
}
secondBlock:^(Bar *b) {
// ...
}];
代码组织
-
函数长度(行数)不应超过2/3屏幕,禁止超过70行。
例外:对于顺序执行的初始化函数,如果其中的过程没有提取为独立方法的必要,则不必限制长度。
单个文件方法数不应超过30个
不要按类别排序(如把IBAction放在一块),应按任务把相关的组合在一起
代码编写顺序:先是life cycle(程序生命周期),然后是Delegate(代理)方法实现,然后是event response(事件响应),然后才是getters and setters(viewOrAttribute的getters和setters)。
属性修饰符
什么时候使用 copy?
block 属性要定义成 copy。
-
当一个属性赋值后不期望改变时应当用 copy,最常见的类型如 NSString、NSURL。
可变类型的成员,如 NSMutableArray、NSMutableDictionary 不能定成 copy 的。
编码风格
不要使用new方法
尽管很多时候能用new
代替alloc init
方法,但这可能会导致调试内存时出现不可预料的问题。Cocoa的规范就是使用alloc init
方法,使用new
会让一些读者困惑。
Public_API要尽量简洁
共有接口要设计的简洁,满足核心的功能需求就可以了。不要设计很少会被用到,但是参数极其复杂的API。如果要定义复杂的方法,使用类别或者类扩展。
引用框架的根头文件
每一个框架都会有一个和框架同名的头文件,它包含了框架内接口的所有引用,在使用框架的时候,应该直接引用这个根头文件,而不是其它子模块的头文件,即使是你只用到了其中的一小部分,编译器会自动完成优化的。
// ******推荐****** //
//引用根头文件
#import
// ******反对****** //
//不要单独引用框架内的其它头文件
#import
#import
BOOL的使用
BOOL在Objective-C中被定义为signed char
类型,这意味着一个BOOL类型的变量不仅仅可以表示YES
(1)和NO
(0)两个值,所以永远不要将BOOL类型变量直接和YES
比较:
// ******推荐****** //
BOOL great = [foo isGreat];
if (great) {
// ...be great!
}
// ******反对****** //
//无法确定|great|的值是否是YES(1),不要将BOOL值直接与YES比较
BOOL great = [foo isGreat];
if (great == YES) {
// ...be great!
}
另外BOOL类型可以和_Bool
,bool
相互转化,但是不能和Boolean
转化。
init和dealloc
推荐的代码组织方式是将 dealloc 方法放在实现文件的最前面(直接在 @synthesize 以及 @dynamic 之后),init 应该跟在 dealloc 方法后面。
当init
dealloc
方法被执行时,类的运行时环境不是处于正常状态的,使用存取方法访问变量可能会导致不可预料的结果,因此应当在这两个方法内直接访问实例变量。
// ******推荐****** //
//直接访问实例变量
- (instancetype)init {
self = [super init];
if (self) {
_bar = [[NSMutableString alloc] init];
}
return self;
}
- (void)dealloc {
[_bar release];
[super dealloc];
}
// ******反对****** //
//不要通过存取方法访问
- (instancetype)init {
self = [super init];
if (self) {
self.bar = [NSMutableString string];
}
return self;
}
- (void)dealloc {
self.bar = nil;
[super dealloc];
}
单例
如果可能,请尽量避免使用单例而是依赖注入。 然而,如果一定要用,请使用一个线程安全的模式来创建共享的实例。对于 GCD,用 dispatch_once() 函数就可以咯。
+ (instancetype)sharedInstance
{
static id sharedInstance = nil;
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
标志
#pragma mark
是一个在类内部组织代码并且帮助你分组方法实现的好办法。 我们建议使用 #pragma mark
- 来分离:
不同功能组的方法
protocols 的实现
对父类方法的重写
- (void)dealloc { /* ... */ }
- (instancetype)init { /* ... */ }
#pragma mark
- View Lifecycle (View 的生命周期)
- (void)viewDidLoad { /* ... */ }
- (void)viewWillAppear:(BOOL)animated { /* ... */ }
- (void)didReceiveMemoryWarning { /* ... */ }
其他
代码文件组织
由于 Xcode 中 Group(黄色文件夹)与实际文件目录中的结构没有强制的关联,这样会导致项目文件中的结构与文件系统中的结构不一致。调整的话需要手工移动文件,再重新加入到 Xcode 项目文件中,比较麻烦。在此,规定保持 App 目录下第一层与第二层目录要一致,再下层不强制要求。
下图是一个示例,右侧是实际文件摆放。
资源文件组织
对于图像,一律要使用 Asset Catalogs 进行管理。文件夹可以使用中文命名,但图像文件必须用英文,如下所示:
注释要求
注释一般用来解释代码的意图。要保持注释和代码同步更新,表达准确,避免误导。尽可能写自注释的代码。尽量使用单行注释而不是块注释。
尽量让代码可以自表述,而不是依赖注释。
注释应该表达那些代码没有表达以及无法表达的东西。如果一段注释被用于解释一些本应该由这段代码自己表达的东西,我们就应该将这段注释看成一个改变代码结构或编码惯例直至代码可以自我表达的信号。我们重命名那些糟糕的方法和类名,而不是去修补。我们选择将长函数中的一些代码段抽取出来形成一些小函数,这些小函数的名字可以表述原代码段的意图,而不是对这些代码段进行注释。尽可能的通过代码进行表达。你通过代码所能表达的和你想要表达的所有事情之间的差额将为注释提供了一个合理的候选使用场合。对那些代码无法表达的东西进行注释,而不要仅简单地注释那些代码没有表达的东西。
块注释
方法内部禁止使用块注释。除非要临时注释大段代码,一般情况总应使用行注释。
因为块注释不能正确嵌套。
忽略没有使用变量的编译警告
对于某些暂时不用,以后可能用到的临时变量,为了避免警告,我们可以使用如下方法将这个警告消除:
- (NSInteger)giveMeFive {
NSString *foo;
#pragma unused (foo)
return 5;
}
手动标明警告和错误
手动明确一个错误:
- (NSInteger)divide:(NSInteger)dividend by:(NSInteger)divisor {
#error Whoa, buddy, you need to check for zero here!
return (dividend / divisor);
}
手动明确一个警告:
- (float)divide:(float)dividend by:(float)divisor {
#warning Dude, don't compare floating point numbers like this!
if (divisor != 0.0) {
return (dividend / divisor);
} else { return NAN;
}
}