小谈OC编码规范

990 阅读10分钟

注释

虽然写起来很痛苦,但注释是保证代码可读性的关键。下面的规则给出了你应该什么时候、在哪进行注释。记住:尽管注释很重要,但最好的代码应该自成文档。与其给类型及变量起一个晦涩难懂的名字,再为它写注释,不如直接起一个有意义的名字。

当你写注释的时候,记得你是在给你的听众写,即下一个需要阅读你所写代码的贡献者。大方一点,下一个读代码的人可能就是你!

对代码块的注释应该更注重说明为什么这么做,而不是做了什么

.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) {
        // ...
    }];