注释
虽然写起来很痛苦,但注释是保证代码可读性的关键。下面的规则给出了你应该什么时候、在哪进行注释。记住:尽管注释很重要,但最好的代码应该自成文档。与其给类型及变量起一个晦涩难懂的名字,再为它写注释,不如直接起一个有意义的名字。
当你写注释的时候,记得你是在给你的听众写,即下一个需要阅读你所写代码的贡献者。大方一点,下一个读代码的人可能就是你!
对代码块的注释应该更注重说明为什么这么做,而不是做了什么。
.h 文件注释
/*!
文本消息类
@discussion 文本消息类,此消息会进行存储并计入未读消息数。
*/
@interface RCTextMessage : RCMessageContent <NSCoding>
@end
之后在业务代码中遇到这个类,按住alt键鼠标点击类名就可以查看到类的Description
属性以及成员变量注释
属性、成员变量、枚举类型的注释建议用 ///< 进行注释
@property (nonatomic, strong) HSMarketIndexModel *USmodel; ///< 美股指数model
@property (nonatomic, strong) HSMarketIndexModel *HKmodel; ///< 港股指数model
@property (nonatomic, strong) HSMarketIndexView *indexView; ///< 指数
同样按住alt键鼠标点击类名也可以查看到类的Description,用/** 指数 */ 这种注释方式也可以看到Description,不过属性太多可能会不太美观。
注备)使用 /** 注释 */ 或者 ///< 注释 视实际情况而定。
代码块注释
善用#pragma mark把代码进行分类,#pragma mark没有下划线,#pragma mark -有下划线分割
建议用如下类似代码块组织代码
#pragma mark - ================ LifeCycle =================
- (void)viewDidLoad {
[self configUI];
...
}
- (void) viewWillAppear:(BOOL)animated {
}...
- (void)configUI {
}
#pragma mark - ================ Public Methods =================
#pragma mark ==== 核心公开方法注释
- (void)somePublicMethod {
}
#pragma mark ==== 核心公开方法注释2
- (void)somePublicMethod2 {
}
#pragma mark - ================ Private Methods =================
#pragma mark ==== 核心私有方法注释
- (void)somePrivateMethod {
}
#pragma mark - ================ UITableView Delegate =================
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
}...
#pragma mark - ================ Actions =================
- (void)someButtonClicked {
}
#pragma mark - ================ Getter and Setter =================
- (void)setModelArray:(NSMutableArray *)modelArray {
}
公开属性以及常量、枚举尽可能的用///<注释(或者/** */),除非特别特别简单的可以省略。
条件语句
条件语句体应该总是被大括号包围。尽管有时候你可以不使用大括号(比如,条件语句体只有一行内容),但是这样做会带来问题隐患。比如,增加一行代码时,你可能会误以为它是 if 语句体里面的。此外,更危险的是,如果把 if 后面的那行代码注释掉,之后的一行代码会成为 if 语句里的代码。
Good:
if (!error) {
return success;
}
if (!error) {
return success;
}
else {//else 换行
// xxxx
}
Avoid:
if (!error)
return success;
和
if (!error) return success;
尤达表达式
不要使用尤达表达式。尤达表达式是指,拿一个常量去和变量比较而不是拿变量去和常量比较。
Good:
if ([myValue isEqual:@42]) { ...
Avoid:
if ([@42 isEqual:myValue]) { ...
黄金大道
Good:
- (void)someMethod {
if (![someOther boolValue]) {
return;
}
// Do something important
}
Avoid:
- (void)someMethod {
if ([someOther boolValue]) {
// Do something important
}
}
复杂的表达式
当你有一个复杂的 if 子句的时候,你应该把它们提取出来赋给一个 BOOL 变量,这样可以让逻辑更清楚,而且让每个子句的意义体现出来。
BOOL nameContainsSwift = [sessionName containsString:@"Swift"];
BOOL isCurrentYear = [sessionDateCompontents year] == 2014;
BOOL isSwiftSession = nameContainsSwift && isCurrentYear;
if (isSwiftSession) {
// Do something very cool
}
三元运算符
三元运算符 ? 应该只用在它能让代码更加清楚的地方。 一个条件语句的所有的变量应该是已经被求值了的。类似 if 语句,计算多个条件子句通常会让语句更加难以理解。或者可以把它们重构到实例变量里面。
Good:
result = a > b ? x : y;
Avoid:
result = a > b ? x = c > d ? c : d : y;
当三元运算符的第二个参数(if 分支)返回和条件语句中已经检查的对象一样的对象的时候,下面的表达方式更灵巧:
Good:
result = object ? : [self createObject];
Avoid:
result = object ? object : [self createObject];
Case语句
除非编译器强制要求,括号在 case 语句里面是不必要的。但是当一个 case 包含了多行语句的时候,需要加上括号。
switch (condition) {
case 1:
// ...
break;
case 2: {
// ...
// Multi-line example using braces
break;
}
case 3:
// ...
break;
default:
// ...
break;
}
有时候可以使用 fall-through 在不同的 case 里面执行同一段代码。一个 fall-through 是指移除 case 语句的 “break” 然后让下面的 case 继续执行。
switch (condition) {
case 1:
case 2:
// code executed for values 1 and 2
break;
default:
// ...
break;
}
当在 switch 语句里面使用一个可枚举的变量的时候,default 是不必要的。比如:
switch (menuType) {
case ZOCEnumNone:
// ...
break;
case ZOCEnumValue1:
// ...
break;
case ZOCEnumValue2:
// ...
break;
}
此外,为了避免使用默认的 case,如果新的值加入到 enum,程序员会马上收到一个 warning 通知
命名
通用的约定
尽可能遵守 Apple 的命名约定,
常量
常量应该以驼峰法命名,并以相关类名作为前缀。
Good:
static const NSTimeInterval ZOCSignInViewControllerFadeOutAnimationDuration = 0.4;
Avoid:
static const NSTimeInterval fadeOutTime = 0.4; 推荐使用常量来代替字符串字面值和数字,这样能够方便复用,而且可以快速修改而不需要查找和替换。常量应该用 static 声明为静态常量,而不要用 #define,除非它明确的作为一个宏来使用。
Good:
static NSString * const ZOCCacheControllerDidClearCacheNotification = @"ZOCCacheControllerDidClearCacheNotification";
static const CGFloat ZOCImageThumbnailHeight = 50.0f;
Avoid:
#define CompanyName @"Apple Inc."
#define magicNumber 42
常量应该在头文件中以这样的形式暴露给外部:
extern NSString *const ZOCCacheControllerDidClearCacheNotification;
并在实现文件中为它赋值。
只有公有的常量才需要添加命名空间作为前缀。尽管实现文件中私有常量的命名可以遵循另外一种模式,你仍旧可以遵循这个规则。
方法
方法名与方法类型 (-/+ 符号)之间应该以空格间隔。方法段之间也应该以空格间隔(以符合 Apple 风格)。参数前应该总是有一个描述性的关键词。
私有方法也不能以_开头,因为这是C++标准,且“_”前缀是Apple保留的,不要冒重载苹果的私有方法的险。
尽可能少用 "and" 这个词。它不应该用来阐明有多个参数,比如下面的 initWithWidth:height: 这个例子:
Good:
- (void)setExampleText:(NSString *)text image:(UIImage *)image;
- (void)sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag;
- (id)viewWithTag:(NSInteger)tag;
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;
Avoid:
- (void)setT:(NSString *)text i:(UIImage *)image;
- (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag;
- (id)taggedView:(NSInteger)tag;
- (instancetype)initWithWidth:(CGFloat)width andHeight:(CGFloat)height;
- (instancetype)initWith:(int)width and:(int)height; // Never do this.
类
类名称命名规范:使用统一的前缀(QYC),故统一使用QYC作为前缀,控制器统一使用VC作为结尾,UITableViewCell使用Cell结尾,UICollectionViewCell使用CollectionCell,结尾UIView使用统一的View作为结尾,其他对应的类使用其本来的名称作为后缀名。保持整个项目的统一。
属性
属性名称规范:描述的单词+变量类型,一目了然,且采用小驼峰命名法
UILabel *nameLabel
UIButton *selectBtn
UITextField *nameTextField
Block中避免 self 的循环引用
当使用代码块和异步分发的时候,要注意避免引用循环。 总是使用 weak 来引用对象,避免引用循环。(更为优雅的方式是采用影子变量@weakify/@strongify) 此外,把持有 block 的属性设置为 nil (比如 self.completionBlock = nil) 是一个好的实践。它会打破 block 捕获的作用域带来的引用循环。
例子:
__weak __typeof(self) weakSelf = self;
[self executeBlock:^(NSData *data, NSError *error) {
[weakSelf doSomethingWithData:data];
}];
不要这样:
[self executeBlock:^(NSData *data, NSError *error) {
[self doSomethingWithData:data];
}];
多个语句的例子:
__weak __typeof(self)weakSelf = self;
[self executeBlock:^(NSData *data, NSError *error) {
__strong __typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf) {
[strongSelf doSomethingWithData:data];
[strongSelf doSomethingWithData:data];
}
}];
不要这样:
__weak __typeof(self)weakSelf = self;
[self executeBlock:^(NSData *data, NSError *error) {
[weakSelf doSomethingWithData:data];
[weakSelf doSomethingWithData:data];
}];
你应该把这两行代码作为 snippet 加到 Xcode 里面并且总是这样使用它们。
__weak __typeof(self)weakSelf = self;
__strong __typeof(weakSelf)strongSelf = weakSelf;
代码格式
空格
指针"*"号的位置在变量名前,而非变量类型之后,与变量名无空格相连,与类型间有个空格:
@property (nonatomic, strong) NSString* name; // avoid
@property (nonatomic, strong) NSString *password; // good
"{"和"("前均需要一个空格,“}”后如果紧跟着内容比如else,也需要一个空格,但“(”和“[”右边,“)”和“]”左边不能有空格。涉及位置:if-else,while,方法声明和实现,@interface等。
-(void)viewDidLoad{ // avoid
[super viewDidLoad];
if([self isOk]){ // avoid
// ...
}else{ // avoid
// ...
}
}
- (void)viewDidLoad { // good
[super viewDidLoad];
if ([self isOk]) { // good
// ...
} else { // good
// ...
}
}
除了++和--外的运算符前后均需要一个空格。
if ( i>10 ) { // avoid
i ++; // avoid
} else {
i+=2; // avoid
}
if (i > 10) { // good
i++;
} else {
i += 2;
}
“,”左边不留空格,右边空一格
NSArray *array = @[@"A" , @"B" ,@"C"]; // avoid
NSArray *array = @[@"A", @"B", @"C"]; // good
括号
if、else后面必须紧跟"{ }",即便只有一行代码。 防止之后增添逻辑时忽略增加{},逻辑代码跑到if、else之外,同时更便于阅读。
if (i > 10) // avoid
i++;
else
i--;
if (i > 10) { // good
i++;
} else {
i--;
}
[UIView animateWithDuration:1.0 animations:^{
// ...
} completion:^(BOOL finished) {
// ...
}];
花括号统一采用不换行风格。涉及位置:@interface、方法实现、if-else、switch-case等。
if-else中,else到底是否另起一行,待商讨,但是不管采用哪一种,应该是全局统一的。
if (i > 10) { // good
i++;
}
else {
i--;
}
换行
避免使用两行以上的空行,建议#import、@interface、@implementation、方法与方法之间以两行空行作为间隔。
方法内可使用一行空行来分离不同功能的代码块,但通常不同功能代码块应该考虑抽取新的方法。
包含3个及以上参数的方法签名,建议对每个参数进行换行,参数按照冒号对其,让代码具有更好可读性,也便于修改。
// avoid
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion {
// ...
}
// good
+ (void)animateWithDuration:(NSTimeInterval)duration
animations:(void (^)(void))animations
completion:(void (^)(BOOL finished))completion {
// ...
}
在分行时,如果第一段名称过短,后续名称可以以 Tab 的长度(4个空格)为单位进行缩进:
- (void)short:(GTMFoo *)theFoo
longKeyword:(NSRect)theRect
evenLongerKeyword:(float)theInterval
error:(NSError **)theError {
...
}
函数调用
函数调用的格式和书写差不多,可以按照函数的长短来选择写在一行或者分成多行:
// 写在一行
[myObject doFooWith:arg1 name:arg2 error:arg3];
// 分行写,按照':'对齐
[myObject doFooWith:arg1
name:arg2
error:arg3];
// 第一段名称过短的话后续可以进行缩进
[myObj short:arg1
longKeyword:arg2
evenLongerKeyword:arg3
error:arg4]
闭包(Blocks)
- 根据 block 的长度,有不同的书写规则:
- 较短的 block 可以写在一行内。
- 如果分行显示的话,block 的右括号 } 应该和调用 block 那行代码的第一个非空字符对齐。
- block 内的代码采用4个空格的缩进。
- 如果 block 过于庞大,应该单独声明成一个变量来使用。
- ^ 和 ( 之间,^ 和 { 之间都没有空格,参数列表的右括号 ) 和 { 之间有一个空格。
// 较短的block写在一行内
[operation setCompletionBlock:^{ [self onOperationDone]; }];
// 分行书写的block,内部使用4空格缩进
[operation setCompletionBlock:^{
[self.delegate newDataAvailable];
}];
// 使用C语言API调用的block遵循同样的书写规则
dispatch_async(_fileIOQueue, ^{
NSString *path = [self sessionFilePath];
if (path) {
// ...
}
});
// 较长的block关键字可以缩进后在新行书写,注意block的右括号'}'和调用block那行代码的第一个非空字符对齐
[[SessionService sharedService]
loadWindowWithCompletionBlock:^(SessionWindow *window) {
if (window) {
[self windowDidLoad:window];
} else {
[self errorLoadingWindow];
}
}];
// 较长的block参数列表同样可以缩进后在新行书写
[[SessionService sharedService]
loadWindowWithCompletionBlock:
^(SessionWindow *window) {
if (window) {
[self windowDidLoad:window];
} else {
[self errorLoadingWindow];
}
}];
// 庞大的block应该单独定义成变量使用
void (^largeBlock)(void) = ^{
// ...
};
[_operationQueue addOperationWithBlock:largeBlock];
// 在一个调用中使用多个block,注意到他们不是像函数那样通过':'对齐的,而是同时进行了4个空格的缩进
[myObject doSomethingWith:arg1
firstBlock:^(Foo *a) {
// ...
}
secondBlock:^(Bar *b) {
// ...
}];