iOS代码规范
一、前言
本规范基于Google Objective-C Style Guide和百度Objective-C规范和实际开发情况,对其中的说明性语句及非ARC部分进行了删减。
每项规范前面的[强制]代表该规范需要强制执行,[建议]代表推荐执行但不强制。
本文章里面的代码格式大部分内容可以通过插件自动格式化,详见ClangFormat-Xcode插件使用。
二、缩进与格式
1、缩进符
- [强制] 只用空格,用4个空格表示一个缩进。
2、每行的长度
- [强制] 应尽量控制每行代码的长度在120个字符以内。
3、逗号分隔项
- [强制] 用逗号分隔多项时,每个逗号后使用1个空格进行分隔。
4、左大括号位置
- [强制] 左大括号 { 不单独占据一行,放置在上一行的末尾,可以在 { 前增加一个空格。
5、声明与定义
- [强制] -,+ 与返回类型之间必须有一个空格,在参数列表中,除了参数之间不要有任何间距。
示例
- (void)doSomethingWithString:(NSString *)theString {
...
}
[强制] * 号前面必须要加空格,增加可读性。
[建议] 如果有参数太多无法放在同一行内, 最好每个参数各自一行。如果采用多行,每个参数建议按照 : 进行对齐。
示例
- (void)doSomethingWith:(GTMFoo *)theFoo
rect:(NSRect)theRect
interval:(float)theInterval {
...
}
- [建议] 当第一个关键词比其他的短时,可以缩进之后的行至少4个空格,同样按 : 进行对齐。
示例
- (void)short:(GTMFoo *)theFoo
longKeyword:(NSRect)theRect
evenLongerKeyword:(float)theInterval
error:(NSError **)theError {
...
}
6、方法调用
- [建议] 方法调用的格式须要与定义时的格式一致。当有多种格式化的样式可供选择的时候,按照惯例,采用在给定的源文件中使用过的那个方式。
- [建议] 所有的参数都应该在同一行。
示例
[myObject doFooWith:arg1 name:arg2 error:arg3];
或者每个参数一行,并按 : 对齐。
[myObject doFooWith:arg1
name:arg2
error:arg3];
不建议采用以下风格:
[myObject doFooWith:arg1 name:arg2 //这行写了两个参数
error:arg3];
[myObject doFooWith:arg1
name:arg2 error:arg3];
就像声明和定义一样,当第一个关键词比其他的短时,可以缩进之后的行至少4个空格,同样按 : 进行对齐:
[myObj short:arg1
longKeyword:arg2
evenLongerKeyword:arg3
error:arg4];
7、@public、@protected与@private
- [强制] 访问修饰符 @public, @private,@protected必须缩进2个空格。
8、异常
- [建议] 在单独一行时,使用 @ 标签格式化 exceptions, @ 标签和左大括号 { 间加一个空格,在 @catch 和 对象捕获声明之间也一样。建议遵循下面的格式:
@try {
foo();
} @catch (NSException *ex) {
bar(ex);
} @finally {
baz();
}
9、协议
- [强制] 在类型标识符和封装在尖括号中的 Protocols 名称之间要有空格。
@interface MyProtocoledClass : NSObject <NSWindowDelegate> {
@private id <MyFancyDelegate> delegate_;
}
(void)setDelegate:(id <MyFancyDelegate>)aDelegate;
@end
解释:这些适用于类的声明、实例变量和方法的声明。
10、Blocks
- [强制] 块内的代码应缩进4个空格。
- [建议] 因为具体block长度的不同,可以有以下几种风格:
(1)如果block一行就能放下,就不需要换行。
(2)如果必须要换行,那么 } 需要和block所在行的第一个字符对齐。
(3)block中的代码块应该缩进4个空格。
(4)如果block很大,比如超过20行,建议单独拿出来赋给一个本地变量
(5)如果block没有参数,那字符 ^{ 之间就不应有空格。如果有参数,字符 ^{ 之间同样没有空格,但是字符 ) { 之间需要有一个空格。
(6)包含内联block的函数调用可以在缩进4个空格的基础上左对齐,尤其是调用中包含多个内联block。
示例
//整个block放在一行的
[operation setCompletionBlock:^{ [self onOperationDone]; }];
//多行时缩进四个空格,{要和block所在行的第一个字符对齐
[operation setCompletionBlock:^{
[self.delegate newDataAvailable];
}];
//在C函数中使用block时遵循和Objective-C同样的对齐和缩进原则
dispatch_async(fileIOQueue_, ^{
NSString *path = [self sessionFilePath];
if (path) { // ...
}
});
// 方法参数与block声明能放到一行时。注意比较^(SessionWindow *window) {和上面的^{。
[[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。
[myObject doSomethingWith:arg1
firstBlock:^(Foo *a) { // ...
}
secondBlock:^(Bar *b){
// ...
}];
11、Container Literals
- [强制] 使用容器(数组和字典)常量,如果其内容被分为多行,应该缩进4个空格。
- [建议] 如果内容单行就能放下,在左大括号之后和右大括号之前各添加一个空格。
NSArray *array = @[ [foo description], @"Another String", [bar description] ];
NSDictionary *dict = @{
NSForegroundColorAttributeName : [NSColor redColor]
};
- [建议] 如果内容跨越多行,将左括号和声明放在同一行,之后换行的内容缩进4个空格,并将右括号单独放在新的一行里和声明行对齐。
NSArray *array = @[
@"This",
@"is",
@"an",
@"array"
];
NSDictionary *dictionary = @{
NSFontAttributeName : [NSFont fontWithName:@"Helvetica-Bold" size:12],
NSForegroundColorAttributeName : fontColor
};
三、命名与规范
在撰写纯粹的Objective-C代码时,推荐使用驼峰命名法。
1、文件名
文件名应该反映其中包含的类实现的名称,按照项目中的约定且大小写相关。
- [强制] 实现Category的文件名需包含类名,如 NSString+Utils.h 或 NSTextView+Autocomplete.h。
2、Objective-C++
在一个源码文件中, Objective-C++ 遵循你实现的函数/方法的风格。
为了最小化在混合开发Cocoa/Objective-C和C++时由命名风格造成的冲突,遵循正在实现方法的风格。如果正在实现的方法是在@implementation 块中, 使用Objective-C的命名规范。如果正在实现的方法是在C++的class中,则采用C++的命名规范。
class CrossPlatformAPI {
public:
... int DoSomethingPlatformSpecific(); // 每个平台的实现都不一样
private:
int an_instance_var_;
};
// 文件 mac_implementation.mm
#include "cross_platform_header.h" //
// 典型的Objective-C class, 使用Objective-C命名规范。
@interface MyDelegate :
NSObject {
@private
int _instanceVar;
CrossPlatformAPI *_backEndObject;
}
- (void)respondToSomething:(id)something;
@end
@implementation MyDelegate
- (void)respondToSomething:(id)something {
//从Cocoa桥接到C++的后端
_instanceVar = _backEndObject->DoSomethingPlatformSpecific();
NSString *tempString = [NSString stringWithFormat:@"%d", _instanceVar];
NSLog(@"%@", tempString);
}
@end
// C++ class平台相关的实现, 使用C++命名规范
int CrossPlatformAPI::DoSomethingPlatformSpecific() {
NSString *temp_string = [NSString stringWithFormat:@"%d", an_instance_var_];
NSLog(@"%@", temp_string);
return [temp_string intValue];
}
3、类名
- [强制] 类名(包括Category、Protocol名和Block名)以大写字母开始,通过大小写而不是下划线分隔。
- [建议] 在设计可跨越多个应用之间共享的代码时,推荐使用前缀,(例如,GTMSendMessage)。还有很多外部依赖库的大型应用中设计的类最好也使用前缀。
- [建议] 若使用前缀,则前缀缩写要大于2个字符。
- [建议] Protocal和Block名称如果和类相关建议加类名前缀。
示例
@protocol AdvertisingViewDelegate <NSObject>
- (void)didSelectImageAtIndex:(NSInteger)index;
@end
@interface AdvertisingView : UIView
@property (weak, nonatomic) id <AdvertisingViewDelegate> delegate;
@end
4、Category名
- [强制] 类别(Category)名中需要加入被扩展的类名。
比如我们要给NSString类加一个解析的功能,我们创建一个category,命名为GTMStringParsingAdditions,并且放在名为NSString+Parsing.h的文件中。
- [强制] 类名与左括号中间需要有一个空格。
示例
//扩展一个framework类:
@interface NSString (GTMStringParsingAdditions)
- (NSString *)foobarString;
@end
//使方法和属性私有化
@interface FoobarViewController ()
@property(nonatomic, retain) NSView *dongleView;
- (void)performLayout;
@end
5、Objective-C 方法名
- [强制] 方法名应该以小写字母开头,混合大小写。每个命名参数也应该以小写字母开头。
- [建议] 方法名称应该尽可能读起来像句子,意味着你应该选择能够搭配方法名的参数名。(比如:convertPoint:fromRect:或replaceCharactersInRange:withString:)。
- [建议] getter类型的方法不加get前缀。
示例
- (id)getDelegate; // 应避免的
- (id)delegate; // 推荐的
- [建议] 在所有参数前面添加关键字。
示例
- (void)sendAction:(SEL)aSelector toObject:(id)anObject forAllCells:(BOOL)flag; // 推荐的
- (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag; // 错误的
- [建议] 确保参数前面的关键字可以正确描述参数。
示例
- (id)viewWithTag:(NSInteger)aTag; // 推荐的
- (id)taggedView:(int)aTag; // 错误的
- [强制] 当需要基于已有的一个方法创建新方法时,请将新的关键字添加到原有方法后面。
示例
- (id)initWithFrame:(CGRect)frameRect; // 原有方法
- (id)initWithFrame:(NSRect)frameRect
mode:(int)aMode
cellClass:(Class)factoryId
numberOfRows:(int)rowsHigh
numberOfColumns:(int)colsWide; // 新方法
- [建议] 尽量不要使用“and” 描述参数。
示例
// 推荐的
(int)runModalForDirectory:(NSString *)path file:(NSString *) name types:(NSArray *)fileTypes;
//错误的
(int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes;
6、变量名
- [强制] 变量名以小写字母开头,混合大小写以区分单词。
6.1、普通变量名
- [建议] 不要使用匈牙利命名法,比如不要使用变量的静态类型(int 或 pointer)。尽量写具有描述意义的名称。比如不要使用下面的变量全名:
int w;
int nerr;
int nCompConns;
tix = [[NSMutableArray alloc] init];
obj = [someObject object];
p = [network port];
建议使用如下的变量命名:
int numErrors;
int numCompletedConnections;
tickets = [[NSMutableArray alloc] init];
userInfo = [someObject object];
port = [network port];
6.2、类成员变量
- [建议] 类成员变量的命名是在普通变量的名字前,添加一个下划线做前缀,比如_usernameTextField。
6.3、常量
- [强制] 常量名(宏,本地常变量等)首字母应该以小写的k开头,然后使用混合大小写区分单词,枚举值前面需要以枚举名为前缀,前面加E。
示例
const int kNumberOfFiles = 12;
NSString *const kUserKey = @"kUserKey";
enum EDisPlayTinge {
EDisplayTingeGreen = 1 E
DisplayTingeBlue = 2
};
6.4、静态变量
- [强制]静态变量应该以s或者以shared开头使用混合大小写命名。
示例
static MyClass *sharedInstance = nil;
static MyClass *sMyClassInstance = nil;
7、图片命名
- [强制]命名规则为「模块+类型+一级属性+二级属性」。
示例
「tabbar_btn_red_n.png」 //tabbar 导航栏,btn 按钮,n normal 状态
- [强制]图片应该为PNG文件,且需要放入images.scassets中。
- [强制]图片必须包含@2x图,如果只有@3x图需要使用插件转换为@2x图片。
- [建议]images.scassets应该安照模块进行分组,方便查找。
四、注释
1、文件注释
- [建议] 创建文件时会生成默认的注释。如果看了文件名还不懂该文件是干什么,可以有选择的在一个文件开头写一段关于内容的描述。
2、声明部分的注释
- [建议] 每个接口,类别,协议的声明都应该有个伴随的注释,来描述它的作用以及它如何融入整体环境。注释遵循apple doc风格,以/*开始,/结束。
- [强制] 声明部分的注释如果以“//”开头,且与代码同一行,在 “//” 前加空格。
示例
/**
* <#Description#>
* @param nibNameOrNil <#nibNameOrNil description#>
* @param nibBundleOrNil <#nibBundleOrNil description#>
* @return <#return value description#>
*/
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil;
@property(copy) NSString *host; //network host
- [建议] 公共接口的每个方法,都应该有注释来解释它的作用、参数、返回值以及其它影响。
3、实现部分的注释
- [强制] 实现部分的注释如果以“//”开头,且与代码同一行,在 “//“ 前加空格。
五、Cocoa 和 Objective-C 特性
1、重载指定构造函数
- [强制] 当写子类的时候,如果需要init方法,必须重载父类的指定构造函数。
2、重载 NSObject 的方法
- [建议] 如果重载了NSObject类的方法,建议把它们放在@implementation内的前边,这也是惯例。 通常适用(但不局限)于 init...,copyWithZone:,以及dealloc方法。所有init...应放在一起,后面跟着其他NSObject的方法。
3、初始化
- [建议] 不要在 init 方法中,将成员变量初始化为 0或 nil。如果一个对象可被复用,状态需要全部重置,这时可引入 reset方法。
- [强制] 局部变量不会被编译器初始化,所有局部变量使用前必须初始化。
4、避免调用+ new
- [强制] 不要调用NSObject的类方法new,也不要在子类中重载它。使用alloc和init方法创建并初始化对象。
5、保持公共 API 简单
- [建议] 保持公共API简单,避免“包含一切式“的API。
示例
#import "GTMFoo.h"
@interface GTMFoo (PrivateDelegateHandling)
- (NSString *)doSomethingWithDelegate;
// Declare private method
@end
@implementation GTMFoo (PrivateDelegateHandling)
- (NSString *)doSomethingWithDelegate {
// Implement this method
}
@end
- [强制] 可以使用私有Category保证公共头文件整洁。
示例
@interface GTMFoo () {
...
}
6、#import与#include
-[强制] 使用#import来引用Objective-C/Objective-C++头文件,使用#include引用C/C++头文件。
7、使用根框架
- [强制] 包含根框架,而非单独的文件。
示例
#import <Foundation/Foundation.h> // good
#import <Foundation/NSArray.h> // avoid
8、在init和dealloc中避免使用存取方法
- [建议] 在init和dealloc方法执行的过程中,子类可能会处在一个不稳定状态,所以这些方法中应避免调用存取方法,应在这些方法中直接对成员变量进行赋值或释放操作。
示例
- (instancetype)init {
self = [super init];
if (self) {
_bar = [[NSMutableString alloc] init]; // good
}
return self;
}
- (void)dealloc {
[_bar release];
// good
[super dealloc];
}
- (instancetype)init {
self = [super init];
if (self) {
self.bar = [NSMutableString string]; // avoid
}
return self;
}
- (void)dealloc {
self.bar = nil; // avoid
[super dealloc];
}
9、按照声明顺序销毁实例变量
- [建议] dealloc中对象被释放的顺序应该与他们在@interface中声明的顺序一致,这有助于代码检查。
10、setter中对NSString进行copy
- [建议] 接受NSString作为参数的setter,应该copy它所接受的字符串。
示例
- (void)setFoo:(NSString *)aFoo {
[_foo autorelease];
_foo = [aFoo copy];
}
11、避免抛出异常
- [建议] 不要@throw Objective-C 异常,但要注意从第三方的调用或者系统调用捕捉异常。如果确实使用了异常,请注释期望什么方法抛出异常。
12、BOOL的使用
- [强制] 不要直接将 BOOL 值与 YES 、NO进行比较。
- [建议] 对 BOOL 使用逻辑运算符(&&, || 和 !)是合法的,返回值也可以安全地转换成 BOOL。
示例
- (BOOL)isBold {
return [self fontTraits] & NSFontBoldTrait;
}
- (BOOL)isValid {
return [self stringValue];
}
- (BOOL)isBold {
return ([self fontTraits] & NSFontBoldTrait) ? YES : NO;
}
- (BOOL)isValid {
return [self stringValue] != nil;
}
- (BOOL)isEnabled {
return [self isValid] && [self isBold];
}
同样,不要直接比较 YES/NO 和 BOOL 变量。
BOOL great = [foo isGreat]; if (great == YES) { // ...be great! }
BOOL great = [foo isGreat]; if (great) { // ...be great! }
13、属性
- [建议] 属性所关联的实例变量的命名必须遵守以下划线作为前缀的规则。属性的名字应该与成员变量去掉下划线后缀的名字一模一样。@property后面紧跟括号,不留空格。
示例
@interface MyClass : NSObject
@property(copy, nonatomic) NSString *name;
@end
@implementation MyClass
// No code required for auto-synthesis, else use: //
@synthesize name = _name;
@end
14、没有实例变量的接口
- [强制] 没有声明任何实例变量的接口,应省略空大括号。
示例
@interface MyClass : NSObject // Does a lot of stuff
- (void)fooBarBam;
@end
@interface MyClass : NSObject { } // Does a lot of stuff
- (void)fooBarBam;
@end
15、自动synthesize实例变量
- [建议] 优先考虑使用自动 synthesize 实例变量。
示例
@interface Foo : NSObject <Thingy>
// A guy walks into a bar.
@property(nonatomic, copy) NSString *bar;
@end
// Implementation file
@interface Foo ()
@property(nonatomic, retain) NSArray *baz;
@end
@implementation Foo
@synthesize widgetName = _widgetName;
@end
16、数据格式
-[强制]suggest:
NSDictionary *userDic =
@{@"login_phone": theApp.user.phone? theApp.user.phone : @"",
@"timestamp" : timestamp,
@"version" : kAppstore_Version,
@"encoding" : @"utf-8",
@"client_type": @"1",
@"app_code" : VKNetContext.app_ProCode,
@"push_id" : theApp.apnsToken ? theApp.apnsToken : @""};
avoid:
NSNumber *timestamp = [NSNumber numberWithInt:[[NSDate date] timeIntervalSince1970]];
NSDictionary *userDic = @{@"login_phone": theApp.user.phone? theApp.user.phone : @"",@"timestamp" : timestamp,@"version": kAppstore_Version,@"encoding" : @"utf-8",@"client_type": @"1",@"app_code" : VKNetContext.app_ProCode,@"push_id" : theApp.apnsToken ? theApp.apnsToken : @""};
六、性能调优
这里只列一些需要注意的点,具体原理参见iOS应用性能调优的25个建议和技巧
入门级(这是些你一定会经常用在你app开发中的建议)
- 用ARC管理内存
- 在正确的地方使用reuseIdentifier
- 尽可能使Views不透明
- 避免庞大的XIB
- 不要block主线程
- 在Image Views中调整图片大小
- 选择正确的Collection
- 打开gzip压缩
中级(这些是你可能在一些相对复杂情况下可能用到的)
- 重用和延迟加载Views
- Cache, Cache, 还是Cache!
- 权衡渲染方法
- 处理内存警告
- 重用大开销的对象
- 使用Sprite Sheets
- 避免反复处理数据
- 选择正确的数据格式
- 正确地设定Background Images
- 减少使用Web特性
- 设定Shadow Path
- 优化你的Table View
- 选择正确的数据存储选项
进阶级(这些建议只应该在你确信他们可以解决问题和得心应手的情况下采用)
- 加速启动时间
- 使用Autorelease Pool
- 选择是否缓存图片
- 尽量避免日期格式转换