raywenderlich.com 对于搞iOS开发的人来说不会陌生(如果你经常关注一些技术博客的话),它原本只是Ray Wenderlich的个人博客,但通过不断聚集优秀的开发者参与到其站点的技术博客撰写,包括了应用开发和游戏开发的各个方面,同时将这些技术博客整理成书,作为开发教程出售(貌似最近还出视频教程了,又想法圈钱了···),这样raywenderlich.com渐渐发展成了一个iOS开发社区,其优质的文章和对文章本地化的重视,使得其影响力逐渐向全球扩展。本文是对其最近公布的自家的Objective-C代码风格规范的一些整理,原文地址在这里 。
目录
语言
推荐使用美英,主要体现在命名时使用美英单词。
推荐:
1
UIColor * myColor = [ UIColor whiteColor ];
不推荐:
1
UIColor * myColour = [ UIColor whiteColor ];
代码结构
统一使用#pragma mark -
组织代码结构。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#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 {}
空格
使用2个空格缩进(理由是可以在保持打印空白的基础上,尽量减少换行的可能性),不要使用tab缩进,可以在Xcode的preference下进行修改(默认是4个空格);
方法中的{}
和在if
/else
/switch
/while
等语法中出现的{}
在本行开始,而结束于新一行。
推荐:
1
2
3
4
5
if ( user . isHappy ) {
//Do something
} else {
//Do something else
}
不推荐:
1
2
3
4
5
6
7
if ( user . isHappy )
{
//Do something
}
else {
//Do something else
}
方法之间应该有且只有一空行来保持代码结构清晰,方法中的空白行用来划分功能,但是经常你可能需要将它们重构为新的方法;
推荐使用自动提示的语法结构,不过@sythesize
和@dyamic
可以声明在新行;
冒号对齐
这样的方法调用结构要避免,假如方法中出现3个以上的冒号,这样的调用结构会使代码可读性很差。千万别在含有block的方法中去对齐冒号,这样Xcode的缩进会使其非法。
推荐:
1
2
3
4
5
6
// blocks are easily readable
[ UIView animateWithDuration: 1.0 animations: ^ {
// something
} completion: ^ ( BOOL finished ) {
// something
}];
不推荐:
1
2
3
4
5
6
7
8
// colon-aligning makes the block indentation hard to read
[ UIView animateWithDuration: 1.0
animations: ^ {
// something
}
completion: ^ ( BOOL finished ) {
// something
}];
注释
注释主要用来解释这段代码为什么存在于此,注意所有注释需要及时更新或删除。
大段的注释要避免,有必要可以加到单独的文档,注释需要一些简短的说明。PS:不包括那些为生成文档而做的注释(一些工具可以通过代码中的注释生成文档)。
命名
Apple的命名习惯是尽可能详细,尤其是和内存管理相关的。
长的,描述性的方法和变量名命名是被推荐的。
推荐:
1
UIButton * settingsButton ;
不推荐:
类名和常量名必须要有三个字母的命名前缀(主要为了避免和Apple大多数两个前缀的命名冲突,比如UIButton,CAAnimation,CGRect等),不过Core Data的实体命名可以省略这些前缀。比如raywenderlich.com的命名前缀为RTW。
常量需要驼峰型命名,所有单词首字母大写,且使用相关类名作为前缀。
推荐:
1
static NSTimeInterval const RWTTutorialViewControllerNavigationFadeAnimationDuration = 0.3 ;
不推荐:
1
static NSTimeInterval const fadetime = 1.7 ;
property使用驼峰形命名,保证开头单词小写,且使用Apple的自动合成规则,除了特殊情况,不要手动声明@synthesize
。
推荐:
1
@property ( strong , nonatomic ) NSString * descriptiveVariableName ;
不推荐:
下划线
当使用property时,实例变量的读写要使用self.
,这样的话所有的property可以清楚地区分出来。
一个例外:在初始化方法中,需要的实例变量要直接使用_variableName
型,为了避免调用getter/setter方法可能出现的循环引用。
临时变量不能使用下划线。
方法
在方法声明时,方法类型(-/+)后要有一个空格。方法段之间也要有一个空格。在每个变量前要加一个描述性的词语用来描述这个变量。
不要在用于描述的词语中加入“and”,具体例子见下:
推荐:
1
2
3
4
- ( 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 ;
不推荐:
1
2
3
4
5
- ( 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* text
或NSString * text
,除了在声明常量的时候(例如:NSString *const text
)。
如果可能的话,使用私有属性 ,而不是实例变量,尽管使用实例变量也是对的,不过这样的协定可以保持代码的一致性。
只有在初始化方法(如init
,initWithCoder:
等),dealloc
和setter/getter方法中使用下划线加变量名的读写方法,更多关于这一情况的说明见Apple相关文档
推荐:
1
2
3
4
5
@interface RWTTutorial : NSObject
@property ( strong , nonatomic ) NSString * tutorialName ;
@end
不推荐:
1
2
3
@interface RWTTutorial : NSObject {
NSString * tutorialName ;
}
属性
属性需要清楚地列出,属性的property类型顺序应该是先内存相关,再原子性相关,这与从IB中自动关联的属性的顺序是一致的。
推荐:
1
2
@property ( weak , nonatomic ) IBOutlet UIView * containerView ;
@property ( strong , nonatomic ) NSString * tutorialName ;
不推荐:
1
2
@property ( nonatomic , weak ) IBOutlet UIView * containerView ;
@property ( nonatomic ) NSString * tutorialName ;
带有不可变性质的属性(比如:NSString)推荐使用copy
而不是strong
,理由是其他人可能传入一个其对应的可变实例(比如:NSMutableString),而你可能不会注意到。
推荐:
1
@property ( copy , nonatomic ) NSString * tutorialName ;
不推荐:
1
@property ( strong , nonatomic ) NSString * tutorialName ;
点表达式
读写property时应一直使用点表达式,这使代码变得简洁,而[]
表达式用于其他所有的实例中。
推荐:
1
2
3
NSInteger arrayCount = [ self . array count ];
view . backgroundColor = [ UIColor orangeColor ];
[ UIApplication sharedApplication ]. delegate ;
不推荐:
1
2
3
NSInteger arrayCount = self . array . count ;
[ view setBackgroundColor: [ UIColor orangeColor ]];
UIApplication . sharedApplication . delegate ;
文字量
NSString
,NSDictionary
,NSArry
,NSNumber
如果可能的话,尽量使用它们的不可变实例。NSArray
和NSDictionary
不能存在nil
值,否则会引起崩溃。
推荐:
1
2
3
4
NSArray * names = @ [ @"Brian" , @"Matt" , @"Chris" , @"Alex" , @"Steve" , @"Paul" ];
NSDictionary * productManagers = @ { @"iPhone" : @"Kate" , @"iPad" : @"Kamal" , @"Mobile Web" : @"Bill" };
NSNumber * shouldUseLiterals = @ YES ;
NSNumber * buildingStreetNumber = @ 10018 ;
不推荐:
1
2
3
4
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 ];
常量
常量推荐内联的字符串或数字,推荐定义为static
变量,除非要作为宏,不然不要使用#define
。
推荐:
1
2
3
static NSString * const RWTAboutViewControllerCompanyName = @"RayWenderlich.com" ;
static CGFloat const RWTImageThumbnailHeight = 50.0 ;
不推荐:
1
2
3
#define CompanyName @"RayWenderlich.com"
#define thumbnailHeight 2
枚举类型
当使用枚举型时,推荐使用新的固定基础类型定义,理由是有更强的类型检查和代码完成功能。SDK现在包含了一个宏来确保和推广使用固定基础类型:NS_ENUM()
。
例如:
1
2
3
4
5
typedef NS_ENUM ( NSInteger , RWTLeftMenuTopItemType ) {
RWTLeftMenuTopItemMain ,
RWTLeftMenuTopItemShows ,
RWTLeftMenuTopItemSchedule
};
也可以做明确的赋值
1
2
3
4
5
6
typedef NS_ENUM ( NSInteger , RWTGlobalConstants ) {
RWTPinSizeMin = 1 ,
RWTPinSizeMax = 5 ,
RWTPinCountMin = 100 ,
RWTPinCountMax = 500 ,
};
旧式的k型常量定义只被用于书写CoreFoundation的C代码中。
不推荐:
1
2
3
4
enum GlobalConstants {
kMaxPinSize = 5 ,
kMaxPinCount = 500 ,
};
分支语句
花括号对于分支语句并不是必须的,除非编译器强制使用。
当一个分支包含多于一条语句,花括号需要添加。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
switch ( condition ) {
case 1 :
// ...
break ;
case 2 : {
// ...
// Multi-line example using braces
break ;
}
case 3 :
// ...
break ;
default :
// ...
break ;
}
有时会出现一段代码被多个分支使用,这时就相当于“穿过”。一个“穿过”就是移除这一分支的‘break’语句,使代码继续执行下一分支。“穿过”这种情况需要在代码中进行注释。
1
2
3
4
5
6
7
8
9
10
switch ( condition ) {
case 1 :
// ** fall-through! **
case 2 :
// code executed for values 1 and 2
break ;
default :
// ...
break ;
}
当在switch
语句中使用枚举类型,default
是不需要的:
1
2
3
4
5
6
7
8
9
10
11
12
RWTLeftMenuTopItemType menuType = RWTLeftMenuTopItemMain ;
switch ( menuType ) {
case RWTLeftMenuTopItemMain:
// ...
break ;
case RWTLeftMenuTopItemShows:
// ...
break ;
case RWTLeftMenuTopItemSchedule:
// ...
break ;
}
私有属性
私有属性应该定义到.m
文件中的类扩展(匿名分类)中,命名的分类(如RWTPrivate
,private
)是不允许使用的,除非是在做另一个类的拓展。匿名分类可以暴露和共享于与+Private.h
文件的命名惯例测试中。
例如:
1
2
3
4
5
6
7
@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
,拿nil
来做比较条件是不允许的,永远不要直接拿YES
来比较,因为YES
被定义为1,而一个布尔型可以支持到8比特。
这是为了文件间更多的一致性和更好的可读性。
推荐:
1
2
if ( someObject ) {}
if ( ! [ anotherObject boolValue ]) {}
不推荐:
1
2
3
4
if ( someObject == nil ) {}
if ([ anotherObject boolValue ] == NO ) {}
if ( isAwesome == YES ) {} // Never do this.
if ( isAwesome == true ) {} // Never do this.
如果布尔型被命名为一个形容词,属性名可以省略is
,但是getter方法要保持这一命名。
1
@property ( assign , getter = isEditable ) BOOL editable ;
这一部分来自Cocoa Naming Guidelines 。
条件语句
条件语句主体任何时候都要使用花括号,即使是只有一条语句也需要。这是为了避免错误。这些错误包括添加下一条语句,以为这条语句位于if主体中。另一个更危险的缺点是,if主体内的语句被注释掉,这时下一条语句无意中成为了if语句中的一部分。除此之外,这样的风格与其他条件语句格式保持了一致,便于查找。
推荐:
1
2
3
if ( ! error ) {
return success ;
}
不推荐:
1
2
if ( ! error )
return success ;
或
1
if ( ! error ) return success ;
三元运算符
三元运算符?:
,只有在可以提高可读性和简洁性时才可使。单一的判断条件一般可以使用,当执行多个判断条件时推荐使用if
语句提高可读性,或者将条件重构为实例变量。总的来说,使用三元运算符的最好时机是决定如何给一个变量赋值的时候。
非布尔类型变量需要比较时,要添加()
提高可读性。如果是布尔类型,则不需要。
推荐:
1
2
3
4
5
NSInteger value = 5 ;
result = ( value != 0 ) ? x : y ;
BOOL isHorizontal = YES ;
result = isHorizontal ? x : y ;
不推荐:
1
result = a > b ? x = c > d ? c : d : y ;
Init方法
Init方法遵守Apple生成的代码模板,instancetype
应取代id
作为返回值。
1
2
3
4
5
6
7
- ( instancetype ) init {
self = [ super init ];
if ( self ) {
// ...
}
return self ;
}
关于instancetype
参照Class Constructor Methods 。
构造类方法
当类构造方法使用时,同样需要注意返回值使用instancetype
,而不是id
,这样可确保编译器得知正确的结果类型。
1
2
3
@interface Airplane
+ ( instancetype ) airplaneWithType: ( RWTAirplaneType ) type ;
@end
更多关于instancetype
在NSHipster.com 。
CGRect相关函数
当读取一个CGRect
的x
、y
、width
、height
时,要使用CGGeometry
相关的函数,而不是直接读取结构体。参照Apple的相关文档:
All functions described in this reference that take CGRect data structures as inputs implicitly standardize those rectangles before calculating their results. For this reason, your applications should avoid directly reading and writing the data stored in the CGRect data structure. Instead, use the functions described here to manipulate rectangles and to retrieve their characteristics.
推荐:
1
2
3
4
5
6
7
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 );
不推荐:
1
2
3
4
5
6
7
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 };
愉快路径
进行条件编程时,左边缘的代码应该是愉快路径。也就是说,不要嵌套if
语句。多个return
是允许的。
推荐:
1
2
3
4
5
6
7
- ( void ) someMethod {
if ( ! [ someOther boolValue ]) {
return ;
}
//Do something important
}
不推荐:
1
2
3
4
5
- ( void ) someMethod {
if ([ someOther boolValue ]) {
//Do something important
}
}
异常处理
当方法通过引用的方式返回一个错误参数,使用返回值进行判断,而不是那个错误参数。
推荐:
1
2
3
4
NSError * error ;
if ( ! [ self trySomethingWithError: & error ]) {
// Handle Error
}
不推荐:
1
2
3
4
5
NSError * error ;
[ self trySomethingWithError: & error ];
if ( error ) {
// Handle Error
}
一些Apple的API在成功的情况下向错误参数写入一些垃圾值,所以通过错误参数来判断会带来不良的影响。
单例
单例对象生成共享实例时要注意线程安全。
1
2
3
4
5
6
7
8
9
10
+ ( instancetype ) sharedInstance {
static id sharedInstance = nil ;
static dispatch_once_t onceToken ;
dispatch_once ( & onceToken , ^ {
sharedInstance = [[ self alloc ] init ];
});
return sharedInstance ;
}
这将避免一些多线程下的应用崩溃 。
换行
换行是一个重要的部分,本代码风格规则着重打印和在线的可读性。
例如:
1
self . productsRequest = [[ SKProductsRequest alloc ] initWithProductIdentifiers: productIdentifiers ];
长代码的话,可以进行换行,在第二行开头遵照“空白”一节的规定,需要空两个空格。
1
2
self . productsRequest = [[ SKProductsRequest alloc ]
initWithProductIdentifiers: productIdentifiers ];
笑脸
笑脸是raywenderlich.com站点代码风格的显著特征。使用正确的笑脸表现编程时巨大的喜悦和激动是很重要的。使用方括号笑脸,是因为它是使用ASCII能获得的最大的笑脸···。使用以圆括号结尾的笑脸会出现一个半心形,所以不被推荐···。
推荐:
不推荐:
Xcode工程
物理文件结构和Xcode工程文件结构要保持一致。Xcode项目中的新建分组都要对应文件系统中的文件夹。代码不但要按类型,也要按特征来分组,保证结构更清晰。
可能的话,尽量打开”Treat Warnings as Errors”选项,如果你想忽略一个类型的警告,请查看这里 。
其他Objective-C的代码风格规范