Objective-C Style Guide

这篇风格指南概括了「纽约时代周刊」工作的 iOS 团队的编码约定。我们非常欢迎在 issuespull requests 上给我们反馈。

介绍

这里是一些来自 Apple 的风格指南文档。如果某些没有在本文提及,那在下面这些文档中的某篇会有详细说明:

目录

语言

要求使用美式英语。

推荐:

UIColor *myColor = [UIColor whiteColor];  

不推荐:

UIColor *myColour = [UIColor whiteColor];  

组织代码

使用 #pragma mark - 来把方法按功能分类,协议 / 委托也使用这个基本结构。

#pragma mark - Lifecycle

- (instancetype)init {}
- (void)dealloc {}
- (void)viewDidLoad {}
- (void)viewWillAppear:(BOOL)animated {}
- (void)didReceiveMemoryWarning {}

#pragma mark - Custom Accessors

- (void)setCustomProperty:(id)value {}
- (id)customProperty {}

#pragma mark - IBActions

- (IBAction)submitData:(id)sender {}

#pragma mark - Public

- (void)publicMethod {}

#pragma mark - Private

- (void)privateMethod {}

#pragma mark - Protocol conformance
#pragma mark - UITextFieldDelegate
#pragma mark - UITableViewDataSource
#pragma mark - UITableViewDelegate

#pragma mark - NSCopying

- (id)copyWithZone:(NSZone *)zone {}

#pragma mark - NSObject

- (NSString *)description {}

点语法

点语法应该总是被用来访问要可变属性。中括号语法优先用于其他所有的实例对象。

例如:

view.backgroundColor = [UIColor orangeColor];  
[UIApplication sharedApplication].delegate;

而不是:

[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate;  

空格

  • 使用 4 个空格缩进。如果使用 Tab 键。确保在 Xcode 设置中选中 Tab with width 4 spaces 偏好。
  • 方法间的花括号和其他类型的括号 (if/else/switch/while 等等) 应该始终另起一行。

例如:

if (user.isHappy) {  
    //Do something
} else {
    //Do something else
}
  • 不同的方法之间按照视觉上的一目了然的话,应该是恰好一个空行。在方法的内部应该使用空格来分割功能块,如果要经常这样做的话那你可能有必要去封装一个新的方法了。
  • 推荐使用自动合成功能。但是如果有需要的话,@synthesize and @dynamic 应该在实现文件里互起一行声明。
  • 要避免经常性的使用「冒号对齐」的方法调用。当一个方法名有 >= 3 个冒号的时候再使用「冒号对齐」,这样就能让代码更可读。请永远不要在包含 block 语句的方法里使用「冒号对齐」,因为这样会使代码变得难以辨认。

推荐:

// blocks are easily readable
[UIView animateWithDuration:1.0 animations:^{
    // something
} completion:^(BOOL finished) {
    // something
}];

不推荐:

// colon-aligning makes the block indentation hard to read
[UIView animateWithDuration:1.0
                 animations:^{
                     // something
                 }
                 completion:^(BOOL finished) {
                     // something
                 }];

条件语句

条件语句应该总是使用括号来包含条件主体(即使它只有一行),如果不写入括号中会出现错误。这个错误会把第二行代码包含进 if 语句中。另一方面,在 if 语句内部被注释掉的话还会有更危险的缺陷会发生,下一行就会毫无察觉地成了 if 语句的一部分。此外,这种风格与其他表达式语句一起具有更好的一致性,并且因此更易读。

例如:

if (!error) {  
    return success;
}

而不是:

if (!error)  
    return success;

或者

if (!error) return success;  

三元运算符

三元操作符应该只被用来提高代码的简洁性。单条件语句通常还有待评价。多重条件语句通常比 if 语句,或重构实例变量时更易于理解。

例如:

result = a > b ? x : y;  

而不是:

result = a > b ? x = c > d ? c : d : y;  

错误处理

当方法通过引用返回一个 error 参数的时候,要转换为返回的值,而不是 error 变量。

例如:

NSError *error;  
if (![self trySomethingWithError:&error]) {  
    // Handle Error
}

而不是:

NSError *error;  
[self trySomethingWithError:&error];
if (error) {  
    // Handle Error
}

一些 Apple 的 API 在成功案例(如果非空)处写了一些垃圾值来使参数出错,这样在出现错误的时候会引起一些失败的东西(接着就崩溃了)。

方法

在方法声明中,应该在(-/+ 符号)的后面留一个空格。在方法的分段处也应该留一个空格。

例如::

- (void)setExampleText:(NSString *)text image:(UIImage *)image;

变量

变量应该尽可能的用描述性的语言命名。应该避免使用单字母来命名变量,除了用于 for() 循环。

星号表示变量是一个指针,例如 NSString *text 不要写成 NSString* text。也不见意使用 NSString * text,除非是在定义常量的情况下。

属性定义无论在何时都应该是用来代替直白的实例变量。应该避免直接使用实例变量,除非在初始化方法中(init, initWithCoder:,等等),dealloc 方法和自定义的 settergetter 方法中。想要了解更多关于在初始化方法和 dealloc 中存储器方法信息请访问这里

例如:

@interface NYTSection: NSObject

@property (nonatomic) NSString *headline;

@end

而不是:

@interface NYTSection : NSObject {
    NSString *headline;
}

变量修饰符

当要引入 ARC 使用变量修饰符的时候,修饰符(__strong, __weak, __unsafe_unretained, __autoreleasing)应该放在变量名和星号之间,例如:NSString * __weak text

命名

Apple 命名规范无论何时都应该被遵循,尤其是那些跟内存管理规则 (NARC)相关的。

长的,描述性的方法和变量名才是被推荐的。

例如:

UIButton *settingsButton;  

而不是:

UIButton *setBut;  

三个字母的前缀(例如,NYT)应该用于类名和常量,然后 Core Data 的实体名应该要忽略前缀。常量应该使用驼峰式命名规范,要与类名有相关性要字如其意,并且所有单词的开头字母和前缀都应该大写,

例如:

static const NSTimeInterval NYTArticleViewControllerNavigationFadeAnimationDuration = 0.3;  

而不是

static const NSTimeInterval fadetime = 1.7;  

属性和局部变量应该使用开头字母小写的驼峰式命名规范。

实例变量应该使用开头字母小写的驼峰式命名规范,并且前缀是下划线。这与 LLVM 自动合成的实例变量一致。如果 LLVM 能够自动合成实例变量那就不要手动的去操作

例如:

@synthesize descriptiveVariableName = _descriptiveVariableName;

而不是:

id varnm;  

下划线

当使用属性的时候,实例变量应该总是能够通过 self. 来访问和改变值。这样做能够使所有的属性在视觉上变得更明显。

一个例外情况是:在初始化程序内部,辅助实例变量(例如:_variableName)应该直接用来防止任何 getters/setters 潜在的副作用。

局部变量不应该包含下划线。

注释

当需要注释的时候,注释应该是用来解释为什么的,即这块代码做了哪些事情的详细说明。任何注释要么是最新的要么就删掉。

应该避免使用块注释,并且尽可能的让代码易读,只在有需要的时候用一两行来解释代码做了什么。这项说明不适用于需要用来生成文档的注释。

init 和 dealloc

任何类的 dealloc 方法应该放在 init 的下面。

init 方法的结构应该是这样的:

- (instancetype)init 
{
    self = [super init]; // or call the designated initalizer
    if (self) {
        // Custom initialization
    }

    return self;
}

文字变量

NSString, NSDictionary, NSArray, 和 NSNumber 这些文字变量无论在何时都应该被用来创建不可变的实例对象。特别要留意 nil ,不要把它传入到了 NSArrayNSDictionary 中,因为这样会导致崩溃。

例如:

NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];  
NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"};  
NSNumber *shouldUseLiterals = @YES;  
NSNumber *buildingZIPCode = @10018;  

而不是

NSArray *names = [NSArray arrayWithObjects:@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul", nil];  
NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys: @"Kate", @"iPhone", @"Kamal", @"iPad", @"Bill", @"Mobile Web", nil];  
NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES];  
NSNumber *buildingZIPCode = [NSNumber numberWithInteger:10018];  

CGRect 函数

当访问 x, y, width,或 CGRect 中的 height,总是应该使用 CGGeometry 函数,而不应该直接访问结构的成员。来自 Apple 的 CGGeometry 文档 参考:

在计算那些矩形的结果之前,这篇参考中所提到的所有使用 CGRect 数据结构的函数会使用隐式输入规范。由于这个原因,你的应用应该避免直接读写存储在 CGRect 数据结构中的数据。相反,使用这里的函数写法来操作矩形和检索它们的特征。

例如:

CGRect frame = self.view.frame;

CGFloat x = CGRectGetMinX(frame);  
CGFloat y = CGRectGetMinY(frame);  
CGFloat width = CGRectGetWidth(frame);  
CGFloat height = CGRectGetHeight(frame);  

而不是:

CGRect frame = self.view.frame;

CGFloat x = frame.origin.x;  
CGFloat y = frame.origin.y;  
CGFloat width = frame.size.width;  
CGFloat height = frame.size.height;  

常量

常量比一行数字或文字更好,因为它们能够很容易的被各种变量复制,还可以快速地在不必找到位置再替换的条件下改变值。应该使用 static 来定义常量,并且不要使用 #define 除非明确表明是作为一个宏。

例如:

static NSString * const NYTAboutViewControllerCompanyName = @"The New York Times Company";

static const CGFloat NYTImageThumbnailHeight = 50.0;  

而不是:

#define CompanyName @"The New York Times Company"

#define thumbnailHeight 2

枚举类型

当使用 enum 的时候,通常建议新的固定基础类型规范,因为它具有更强的类型检查和代码自动化。现在的 SDK 已包含了一个宏来来很方便的鼓励固定基础类型的使用 - NS_ENUM()

例如:

typedef NS_ENUM(NSInteger, NYTAdRequestState) {  
    NYTAdRequestStateInactive,
    NYTAdRequestStateLoading
};

Bitmasks

When working with bitmasks, use the NS_OPTIONS macro.

Example:

typedef NS_OPTIONS(NSUInteger, NYTAdCategory) {  
  NYTAdCategoryAutos      = 1 << 0,
  NYTAdCategoryJobs       = 1 << 1,
  NYTAdCategoryRealState  = 1 << 2,
  NYTAdCategoryTechnology = 1 << 3
};

Case 语句

对于 case 语句来说,花括号没必要,除非编译器强制要求。
当一个 case 包含有多行的时候,那必须添加花括号。

例如:

switch (condition) {  
  case 1:
    // ...
    break;
  case 2: {
    // ...
    // 在多行代码时使用花括号
    break;
  }
  case 3:
    // ...
    break;
  default: 
    // ...
    break;
}

有时相同的代码能够用于多种情况下,这时应该使用瀑布流的方式。瀑布流指的是移除了 break 语句的情况,因此可以允许执行流传递到下一个 case 的值。瀑布流的注释应该一目了然。

switch (condition) {  
    case 1:
        // ** 瀑布流!**
    case 2:
        // code executed for values 1 and 2
        break;
    default: 
        // ...
        break;
}

当在 switch 中使用枚举类型的时候,default 并不需要。例如:

RWTLeftMenuTopItemType menuType = RWTLeftMenuTopItemMain;

switch (menuType) {  
    case RWTLeftMenuTopItemMain:
        // ...
        break;
    case RWTLeftMenuTopItemShows:
        // ...
        break;
    case RWTLeftMenuTopItemSchedule:
        // ...
        break;
}

私有属性

私有属性应该在类的实现文件的类扩展处声明(匿名类别)。永远不要使用命名类别(例如:NYTPrivateprivate),除非是用来扩展另一个类。

例如:

@interface NYTAdvertisement ()

@property (nonatomic, strong) GADBannerView *googleAdView;
@property (nonatomic, strong) ADBannerView *iAdView;
@property (nonatomic, strong) UIWebView *adXWebView;

@end

图片命名

图片名字应该与组织和开发者的本意保持一致。它们应该使用驼峰式命名规范,并且使用一个能够描述它们用途的文字来命名。使用无前缀的类名或自定义的属性名开始,然后紧接着是颜色或布局的描述,最后是它们的状态描述。

例如:

  • RefreshBarButtonItem / RefreshBarButtonItem@2xRefreshBarButtonItemSelected / RefreshBarButtonItemSelected@2x
  • ArticleNavigationBarWhite / ArticleNavigationBarWhite@2xArticleNavigationBarBlackSelected / ArticleNavigationBarBlackSelected@2x.

相似用途的图片应该使用文件夹组织起来。

布尔值

既然 nil 等同于 NO 那就没有必要在条件语句中进行比较。永远不要直接把某些东西与 YES 相比较,因为 YES 的定义等同于 1 并且一个 BOOL 相当于 8 位。

这样一来能让整个文件保持一致性,并且具有逻辑性和可读性更好。

例如:

if (!someObject) {  
}

而不是:

if (someObject == nil) {  
}

这里是两个 BOOL 的例子

if (isAwesome)  
if (![someObject boolValue])  

而不是:

if (isAwesome == YES) // 绝不要这样做  
if ([someObject boolValue] == NO)  

BOOL 属性的名字应该取名为形容词,属性名可以忽略 'is' 前缀,但 get 访问方法要使用传统的命名,例如:

@property (assign, getter=isEditable) BOOL editable;

示例参考来自 Cocoa Naming Guidelines

单例

单例对象应该使用安全线程机制来创建它们的共享实例。

+ (instancetype)sharedInstance {
   static id sharedInstance = nil;

   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
      sharedInstance = [[self alloc] init];
   });

   return sharedInstance;
}

这能够防止某些时候可能出现各种程序崩溃的问题

Xcode 工程

为了防止文件散乱,实际文件应该要与 Xcode 工程里的文件保持同步。任何 Xcode 分组的创建都应该能够在文件系统里体现出来。代码文件不仅仅单一地根据类型来分组,而且还可以按更明显的用途来分组。

如果可能的话,请总是在 target's Build Settings 中保持 "Treat Warnings as Errors" 的开启还有尽可能的启用 additional warnings。如果你需要忽略特定的 warning,请使用 Clang's pragma feature

其他的 Objective-C 风格指南

If ours doesn't fit your tastes, have a look at some other style guides: