- 原文链接 : The official raywenderlich.com
Objective-C style guide- 原文作者 : raywenderlich.com Team
- 译文出自 : raywenderlich.com
Objective-C编码规范- 译者 : Sam Lau
因为我正在准备模仿饿了么这个app,到时可能有些iOS开发人员參与进来。
这时假设每一个人的Objective-C编码风格都不一样,这样不易于保持代码一致性和难以Code Review。所以我在网上搜索到 The
official raywenderlich.com Objective-C style guide这篇关于Objective-C编码风格的文章,认为能够作为这个项目的Objective-C的编码标准。所以就翻译这篇文章。
raywenderlich.com Objective-C编码规范
这篇编码风格指南概括了raywenderlich.com的编码规范。可能有些删减或改动。
介绍
我们制定Objective-C编码规范的原因是我们可以在我们的书,教程和刚開始学习的人工具包的代码保持优雅和一致。即使我们有非常多不同的作者来完毕不同的书籍。
这里编码规范有可能与你看到的其它Objective-C编码规范不同,由于它主要是为了打印和web的易读性。
关于作者
这编码规范的创建是由非常多来自raywenderlich.com团队成员在Nicholas Waynik的带领下共同完毕的。团队成员有:Soheil Moayedi Azarpour, Ricardo
Rendon Cepeda,Tony Dahbura, Colin
Eberhardt, Matt Galloway, Greg
Heo, Matthijs Hollemans,Christopher
LaPollo, Saul Mora, Andy
Pereira, Mic Pringle, Pietro
Rea, Cesare Rocchi, Marin
Todorov, Nicholas Waynik和Ray
Wenderlich
我们也很感谢New York Times 和Robots
& Pencils‘Objective-C编码规范的作者。
这两个编码规范为本指南的创建提供非常好的起点。
背景
这里有些关于编码风格Apple官方文档,假设有些东西没有提及,能够在下面文档来查找很多其它细节:
- The
Objective-C Programming Language - Cocoa
Fundamentals Guide - Coding
Guidelines for Cocoa - iOS
App Programming Guide
文件夹
- 语言
- utm_campaign=hugo&utm_medium=reader_share&utm_content=note#code-organization" target="_blank" style="color:rgb(64,148,199); text-decoration:none">代码组织
- 空格
utm_campaign=hugo&utm_medium=reader_share&utm_content=note#comments" target="_blank" style="color:rgb(64,148,199); text-decoration:none">凝视
- 命名
- 方法
- 变量
- 属性特性
- 点符号语法
utm_campaign=hugo&utm_medium=reader_share&utm_content=note#literals" target="_blank" style="color:rgb(64,148,199); text-decoration:none">字面值
- 常量
- 枚举类型
utm_campaign=hugo&utm_medium=reader_share&utm_content=note#case-statements" target="_blank" style="color:rgb(64,148,199); text-decoration:none">Case语句
- 私有属性
utm_campaign=hugo&utm_medium=reader_share&utm_content=note#booleans" target="_blank" style="color:rgb(64,148,199); text-decoration:none">布尔值
- 条件语句
- Init方法
- 类构造方法
- CGRect函数
- 黄金路径
- 错误处理
- 单例模式
- 换行符
- Xcode工程
语言
应该使用US英语.
应该:
UIColor *myColor = [UIColor whiteColor];
不应该:
UIColor *myColour = [UIColor whiteColor];
代码组织
在函数分组和protocol/delegate实现中使用#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 {}
空格
- 缩进使用4个空格,确保在Xcode偏好设置来设置。(raywenderlich.com使用2个空格)
- 方法大括号和其它大括号(
if
/else
/switch
/while
等.)总是在同一行语句打开但在新行中关闭。
应该:
if (user.isHappy) {
//Do something
} else {
//Do something else
}
不应该:
if (user.isHappy)
{
//Do something
}
else {
//Do something else
}
- 在方法之间应该有且仅仅有一行,这样有利于在视觉上更清晰和更易于组织。
在方法内的空白应该分离功能,但通常都抽离出来成为一个新方法。
- 优先使用auto-synthesis。但假设有必要,
@synthesize
和@dynamic
应该在实现中每一个都声明新的一行。 - 应该避免以冒号对齐的方式来调用方法。由于有时方法签名可能有3个以上的冒号和冒号对齐会使代码更加易读。请不要这样做,虽然冒号对齐的方法包括代码块,由于Xcode的对齐方式令它难以辨认。
应该:
// 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
}];
凝视
当须要凝视时,凝视应该用来解释这段特殊代码为什么要这样做。不论什么被使用的凝视都必须保持最新或被删除。
一般都避免使用块凝视,由于代码尽可能做到自解释。仅仅有当断断续续或几行代码时才须要凝视。
例外:这不应用在生成文档的凝视
命名
Apple命名规则尽可能坚持。特别是与这些相关的memory management rules(NARC)。
长的。描写叙述性的方法和变量命名是好的。
应该:
UIButton *settingsButton;
不应该:
UIButton *setBut;
三个字符前缀应该经经常使用在类和常量命名,但在Core Data的实体名中应被忽略。对于官方的raywenderlich.com书、刚開始学习的人工具包或教程,前缀‘RWT‘应该被使用。
常量应该使用驼峰式命名规则,全部的单词首字母大写和加上与类名有关的前缀。
应该:
static NSTimeInterval const RWTTutorialViewControllerNavigationFadeAnimationDuration = 0.3;
不应该:
static NSTimeInterval const fadetime = 1.7;
属性也是使用驼峰式,但首单词的首字母小写。对属性使用auto-synthesis。而不是手动编写@ synthesize语句,除非你有一个好的理由。
应该:
@property (strong, nonatomic) NSString *descriptiveVariableName;
不应该:
id varnm;
下划线
当使用属性时,实例变量应该使用self.
来訪问和改变。这就意味着全部属性将会视觉效果不同。由于它们前面都有self.
。
但有一个特例:在初始化方法里,实例变量(比如。_variableName)应该直接被使用来避免getters/setters潜在的副作用。
局部变量不应该包括下划线。
方法
在方法签名中,应该在方法类型(-/+ 符号)之后有一个空格。
在方法各个段之间应该也有一个空格(符合Apple的风格)。
在參数之前应该包括一个具有描写叙述性的keyword来描写叙述參数。
"and"这个词的使用方法应该保留。它不应该用于多个參数来说明,就像initWithWidth:height
下面这个样例:
应该:
- (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;
不应该:
-(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.
变量
变量尽量以描写叙述性的方式来命名。
单个字符的变量命名应该尽量避免,除了在for()
循环。
星号表示变量是指针。
比如, NSString *text
既不是 NSString*
也不是
textNSString * text
,除了一些特殊情况下常量。
私有变量 应该尽可能取代实例变量的使用。虽然使用实例变量是一种有效的方式,但更偏向于使用属性来保持代码一致性。
通过使用‘back‘属性(_variable,变量名前面有下划线)直接訪问实例变量应该尽量避免,除了在初始化方法(init
, initWithCoder:
,
等…),dealloc
方法和自己定义的setters和getters。想了解关于怎样在初始化方法和dealloc直接使用Accessor方法的很多其它信息。查看这里
应该:
@interface RWTTutorial : NSObject
@property (strong, nonatomic) NSString *tutorialName;
@end
不应该:
@interface RWTTutorial : NSObject {
NSString *tutorialName;
}
属性特性
全部属性特性应该显式地列出来,有助于新手阅读代码。
属性特性的顺序应该是storage、atomicity,与在Interface Builder连接UI元素时自己主动生成代码一致。
应该:
@property (weak, nonatomic) IBOutlet UIView *containerView;
@property (strong, nonatomic) NSString *tutorialName;
不应该:
@property (nonatomic, weak) IBOutlet UIView *containerView;
@property (nonatomic) NSString *tutorialName;
NSString应该使用copy
而不是 strong
的属性特性。
为什么?即使你声明一个NSString
的属性。有人可能传入一个NSMutableString
的实例,然后在你没有注意的情况下改动它。
应该:
@property (copy, nonatomic) NSString *tutorialName;
不应该:
@property (strong, nonatomic) NSString *tutorialName;
点符号语法
点语法是一种非常方便封装訪问方法调用的方式。
当你使用点语法时。通过使用getter或setter方法。属性仍然被訪问或改动。想了解很多其它,阅读这里
点语法应该总是被用来訪问和改动属性,由于它使代码更加简洁。[]符号更偏向于用在其它样例。
应该:
NSInteger arrayCount = [self.array count];
view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate;
不应该:
NSInteger arrayCount = self.array.count;
[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate;
字面值
NSString
, NSDictionary
, NSArray
,
和 NSNumber
的字面值应该在创建这些类的不可变实例时被使用。请特别注意nil
值不能传入NSArray
和NSDictionary
字面值,由于这样会导致crash。
应该:
NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
NSDictionary *productManagers = @{@"iPhone": @"Kate", @"iPad": @"Kamal", @"Mobile Web": @"Bill"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingStreetNumber = @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 *buildingStreetNumber = [NSNumber numberWithInteger:10018];
常量
常量是easy反复被使用和无需通过查找和取代就能高速改动值。
常量应该使用static
来声明而不是使用#define
,除非显式地使用宏。
应该:
static NSString * const RWTAboutViewControllerCompanyName = @"RayWenderlich.com";
static CGFloat const RWTImageThumbnailHeight = 50.0;
不应该:
#define CompanyName @"RayWenderlich.com"
#define thumbnailHeight 2
枚举类型
当使用enum
时,推荐使用新的固定基本类型规格。由于它有更强的类型检查和代码补全。
如今SDK有一个宏NS_ENUM()
来帮助和鼓舞你使用固定的基本类型。
比如:
typedef NS_ENUM(NSInteger, RWTLeftMenuTopItemType) {
RWTLeftMenuTopItemMain,
RWTLeftMenuTopItemShows,
RWTLeftMenuTopItemSchedule
};
你也能够显式地赋值(展示旧的k-style常量定义):
typedef NS_ENUM(NSInteger, RWTGlobalConstants) {
RWTPinSizeMin = 1,
RWTPinSizeMax = 5,
RWTPinCountMin = 100,
RWTPinCountMax = 500,
};
旧的k-style常量定义应该避免除非编写Core Foundation C的代码。
不应该:
enum GlobalConstants {
kMaxPinSize = 5,
kMaxPinCount = 500,
};
Case语句
大括号在case语句中并非必须的,除非编译器强制要求。当一个case语句包括多行代码时。大括号应该加上。
switch (condition) {
case 1:
// ...
break;
case 2: {
// ...
// Multi-line example using braces
break;
}
case 3:
// ...
break;
default:
// ...
break;
}
有非常多次。当同样代码被多个cases使用时。一个fall-through应该被使用。
一个fall-through就是在case最后移除‘break‘语句,这样就行同意运行流程跳转到下一个case值。
为了代码更加清晰,一个fall-through须要凝视一下。
switch (condition) {
case 1:
// ** fall-through! **
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;
}
私有属性
私有属性应该在类的实现文件里的类扩展(匿名分类)中声明,命名分类(比方RWTPrivate
或private
)应该从不使用除非是扩展其它类。匿名分类应该通过使用<headerfile>+Private.h文件的命名规则暴露给測试。
比如:
@interface RWTDetailViewController ()
@property (strong, nonatomic) GADBannerView *googleAdView;
@property (strong, nonatomic) ADBannerView *iAdView;
@property (strong, nonatomic) UIWebView *adXWebView;
@end
布尔值
Objective-C使用YES
和NO
。由于true
和false
应该仅仅在CoreFoundation。C或C++代码使用。
既然nil
解析成NO
,所以没有必要在条件语句比較。不要拿某样东西直接与YES
比較。由于YES
被定义为1和一个BOOL
能被设置为8位。
这是为了在不同文件保持一致性和在视觉上更加简洁而考虑。
应该:
if (someObject) {}
if (![anotherObject boolValue]) {}
不应该:
if (someObject == nil) {}
if ([anotherObject boolValue] == NO) {}
if (isAwesome == YES) {} // Never do this.
if (isAwesome == true) {} // Never do this.
假设BOOL
属性的名字是一个形容词。属性就能忽略"is"前缀。但要指定get訪问器的惯用名称。
比如:
@property (assign, getter=isEditable) BOOL editable;
文字和样例从这里引用Cocoa
Naming Guidelines
条件语句
条件语句主体为了防止出错应该使用大括号包围,即使条件语句主体可以不用大括号编写(如,仅仅用一行代码)。这些错误包含加入第二行代码和期望它成为if语句。还有,even more dangerous defect可能发生在if语句里面一行代码被凝视了。然后下一行代码不知不觉地成为if语句的一部分。
除此之外。这样的风格与其它条件语句的风格保持一致,所以更加easy阅读。
应该:
if (!error) {
return success;
}
不应该:
if (!error)
return success;
或
if (!error) return success;
三元操作符
当须要提高代码的清晰性和简洁性时,三元操作符?
:才会使用。
单个条件求值经常须要它。
多个条件求值时,假设使用if
语句或重构成实例变量时,代码会更加易读。一般来说,最好使用三元操作符是在依据条件来赋值的情况下。
Non-boolean的变量与某东西比較。加上括号()会提高可读性。
假设被比較的变量是boolean类型。那么就不须要括号。
应该:
NSInteger value = 5;
result = (value != 0) ?
x : y;
BOOL isHorizontal = YES;
result = isHorizontal ? x : y;
不应该:
result = a > b ?
x = c > d ?
c : d : y;
Init方法
Init方法应该遵循Apple生成代码模板的命名规则。
返回类型应该使用instancetype
而不是id
- (instancetype)init {
self = [super init];
if (self) {
// ...
}
return self;
}
查看关于instancetype的文章Class Constructor Methods
类构造方法
当类构造方法被使用时,它应该返回类型是instancetype
而不是id
。这样确保编译器正确地判断结果类型。
@interface Airplane
+ (instancetype)airplaneWithType:(RWTAirplaneType)type;
@end
关于很多其它instancetype信息,请查看NSHipster.com
CGRect函数
当訪问CGRect
里的x
, y
, width
,
或 height
时。应该使用CGGeometry
函数而不是直接通过结构体来訪问。引用Apple的CGGeometry
:
在这个參考文档中全部的函数,接受CGRect结构体作为输入。在计算它们结果时隐式地标准化这些rectangles。
因此,你的应用程序应该避免直接訪问和改动保存在CGRect数据结构中的数据。相反,使用这些函数来操纵rectangles和获取它们的特性。
应该:
CGRect frame = self.view.frame;
CGFloat x = CGRectGetMinX(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);
CGRect frame = CGRectMake(0.0, 0.0, width, height);
不应该:
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;
CGRect frame = (CGRect){ .origin = CGPointZero, .size = frame.size };
黄金路径
当使用条件语句编码时,左手边的代码应该是"golden" 或 "happy"路径。也就是不要嵌套if
语句,多个返回语句也是OK。
应该:
- (void)someMethod {
if (![someOther boolValue]) {
return;
}
//Do something important
}
不应该:
- (void)someMethod {
if ([someOther boolValue]) {
//Do something important
}
}
错误处理
当方法通过引用来返回一个错误參数,推断返回值而不是错误变量。
应该:
NSError *error;
if (![self trySomethingWithError:&error]) {
// Handle Error
}
不应该:
NSError *error;
[self trySomethingWithError:&error];
if (error) {
// Handle Error
}
在成功的情况下,有些Apple的APIs记录垃圾值(garbage values)到错误參数(假设non-NULL),那么推断错误值会导致false负值和crash。
单例模式
单例对象应该使用线程安全模式来创建共享实例。
+ (instancetype)sharedInstance {
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
这会防止possible and sometimes prolific crashes.
换行符
换行符是一个非常重要的主题。由于它的风格指南主要为了打印和网上的可读性。
比如:
self.productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
一行非常长的代码应该分成两行代码,下一行用两个空格隔开。
self.productsRequest = [[SKProductsRequest alloc]
initWithProductIdentifiers:productIdentifiers];
Xcodeproject
物理文件应该与Xcodeproject文件保持同步来避免文件扩张。
不论什么Xcode分组的创建应该在文件系统的文件体现。
代码不仅是依据类型来分组,并且还能够依据功能来分组。这样代码更加清晰。
尽可能在target的Build Settings打开"Treat Warnings as Errors。和启用下面additional warnings。假设你须要忽略特殊的警告,使用 Clang‘s
pragma feature。
其它Objective-C编码规范
假设我们的编码规范不符合你的口味,能够查看其它的编码规范:
- Robots & Pencils
- New York Times
- GitHub
- Adium
- Sam Soffes
- CocoaDevCentral
- Luke Redpath
- Marcus Zarra