这篇风格指南概括了「纽约时代周刊」工作的 iOS 团队的编码约定。我们非常欢迎在 issues 和 pull requests 上给我们反馈。
介绍
这里是一些来自 Apple 的风格指南文档。如果某些没有在本文提及,那在下面这些文档中的某篇会有详细说明:
- The Objective-C Programming Language
- Cocoa Fundamentals Guide
- Coding Guidelines for Cocoa
- iOS App Programming Guide
目录
- 语言
- 组织代码
- 点语法
- 空格
- 条件语句
- 错误处理
- 方法
- 变量
- 命名
- 注释
- init 和 dealloc
- 文字变量
- CGRect 函数
- 常量
- 枚举类型
- Bitmasks
- Case 语句
- 私有属性
- [图片命名](#Image Naming)
- 布尔值
- 单例
- Xcode 工程
语言
要求使用美式英语。
推荐:
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
方法和自定义的 setter
和 getter
方法中。想要了解更多关于在初始化方法和 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
,不要把它传入到了 NSArray
和 NSDictionary
中,因为这样会导致崩溃。
例如:
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;
}
私有属性
私有属性应该在类的实现文件的类扩展处声明(匿名类别)。永远不要使用命名类别(例如:NYTPrivate
或 private
),除非是用来扩展另一个类。
例如:
@interface NYTAdvertisement ()
@property (nonatomic, strong) GADBannerView *googleAdView;
@property (nonatomic, strong) ADBannerView *iAdView;
@property (nonatomic, strong) UIWebView *adXWebView;
@end
图片命名
图片名字应该与组织和开发者的本意保持一致。它们应该使用驼峰式命名规范,并且使用一个能够描述它们用途的文字来命名。使用无前缀的类名或自定义的属性名开始,然后紧接着是颜色或布局的描述,最后是它们的状态描述。
例如:
RefreshBarButtonItem
/RefreshBarButtonItem@2x
和RefreshBarButtonItemSelected
/RefreshBarButtonItemSelected@2x
ArticleNavigationBarWhite
/ArticleNavigationBarWhite@2x
和ArticleNavigationBarBlackSelected
/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: