注释规范
文件头注释
文件头注释采用如下格式,该注释由xcode自动生成。如果你对其他人的原始代码作出重大的修改,请把你自己的名字添加到作者里面,并且填写相应的备注。
//
// TestComment.m
// TestVcard
//
// Created by zhangyang on 2017/6/20.
// Copyright © 2017年 myCompany. All rights reserved.
//
修改项目名称和公司名称的方法请参考下图。修改用户名请在系统偏好设置->用户账户中进行修改。
接口注释
每个接口、类别以及协议应辅以注释,以描述它的目的及与整个项目的关系。使用如下格式:
/**
添加描述
@author zhangyang
@date 2017/06/20
*/
可以将该段注释放在代码段中,如下图,我设置了快捷键为cmc。注意添加完成后需要修改描述和日期。
变量和常量注释
下面几种情况下的常量和变量,都要添加注释说明,在上方添加注释。//和内容后面有一个空格。
- 接口中定义的所有常量
- 公有类的公有常量
- 枚举类定义的所有枚举常量
- 实体类的所有属性变量
// 用户名
@property (nonatomic, strong) NSString *userId;
//用户密码
@property (nonatomic, strong) NSString *userPwd;
方法注释
xcode 8已经集成了VVDocument,直接使用该注释格式。所有非系统方法都需要添加注释,包括在头文件和实现文件中。在相应的方法上方使用快捷键alt+command+/
,即可生成该方法的注释:
/**
注册用户
@param userId 用户id
@param userPwd 用户密码
@return 0 成功 其他 失败
*/
- (NSInteger)registerUser:(NSString *)userId userPwd:(NSString *)userPwd;
注意,如果该方法所属的文件是他人创建的,需要添加上用户和日期。如果该方法不是与文件在同一时间创建的,也需要添加时间:
/**
注册用户
@author zhangyang
@date 2017/06/20
@param userId 用户id
@param userPwd 用户密码
@return 0 成功 其他 失败
*/
- (NSInteger)registerUser:(NSString *)userId userPwd:(NSString *)userPwd;
代码组织分类
在函数分组和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个空格
花括号书写
- 左大括号前不换行
- 左大括号后换行
- 右大括号前换行
- 如果右大括号是一个语句、函数体或类的终止,则右大括号后换行; 否则不换行
例如,如果右大括号后面是else或逗号,则不换行。
空格的使用
if、else、for、switch、while等逻辑关键字与后面的语句留一个空格隔开。
if (isSuccess) {
// TODO while booleanVariable is true
} else {
// TODO else
}
运算符两边各用一个空格隔开。
int result = a + b; //Good, = 和 + 两边各用一个空格隔开
int result=a+b; //Bad,=和+两边没用空格隔开
for (int i = 0; i < 10; i++); //good
for (int i=0;i<10;i++); //bad
属性定义括号与前后各有一个空格,指针的*号紧靠变量名。如下所示:
@property (strong, nonatomic) UIWindow *window;
行宽
尽量让你的代码保持在 80 列之内(或者100)。在xcode中的设置方式如下:
方法声明和定义
Tip
- + 和返回类型之间须使用一个空格,参数列表中只有参数之间可以有空格。
方法应该像这样:
- (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 {
...
}
方法调用
Tip
方法调用应尽量保持与方法声明的格式一致。当格式的风格有多种选择时,新的代码要与已有代码保持一致。
调用时所有参数应该在同一行:
[myObject doFooWith:arg1 name:arg2 error:arg3];
或者每行一个参数,以冒号对齐:
[myObject doFooWith:arg1
name:arg2
error:arg3];
不要使用下面的缩进风格:
[myObject doFooWith:arg1 name:arg2 // some lines with >1 arg
error:arg3];
[myObject doFooWith:arg1
name:arg2 error:arg3];
[myObject doFooWith:arg1
name:arg2 // aligning keywords instead of colons
error:arg3];
方法定义与方法声明一样,当关键字的长度不足以以冒号对齐时,下一行都要以四个空格进行缩进。
[myObj short:arg1
longKeyword:arg2
evenLongerKeyword:arg3];
@public和 @private
Tip
@public和 @private访问修饰符应该以一个空格缩进。
与 C++ 中的 public, private以及 protected非常相似。
@interface MyClass : NSObject {
@public
...
@private
...
}
@end
异常
Tip
每个 @标签应该有独立的一行,在 @与 {}之间需要有一个空格,@catch
与被捕捉到的异常对象的声明之间也要有一个空格。
如果决定使用 Objective-C 的异常,那么就按下面的格式。不过最好先看看 避免抛出异常 了解下为什么不要使用异常。
@try {
foo();
}
@catch (NSException *ex) {
bar(ex);
}
@finally {
baz();
}
协议名
Tip
类型标识符和尖括号内的协议名之间,不能有任何空格。
这条规则适用于类声明、实例变量以及方法声明。例如:
@interface MyProtocoledClass : NSObject<NSWindowDelegate> {
@private id<MyFancyDelegate> delegate_;
}
- (void)setDelegate:(id<MyFancyDelegate>)aDelegate;
@end
块(闭包)
Tip
块(block)适合用在 target/selector 模式下创建回调方法时,
因为它使代码更易读。块中的代码应该缩进 4 个空格。
取决于块的长度,下列都是合理的风格准则:
- 如果一行可以写完块,则没必要换行。
- 如果不得不换行,关括号应与块声明的第一个字符对齐。
- 块内的代码须按 4 空格缩进。
- 如果块太长,比如超过 20 行,建议把它定义成一个局部变量,然后再使用该变量。
- 如果块不带参数,^{ 之间无须空格。如果带有参数,^( 之间无须空格,但 ) { 之间须有一个空格。
// The entire block fits on one line.
[operation setCompletionBlock:^{ [self onOperationDone]; }];
// The block can be put on a new line, indented four spaces, with the
// closing brace aligned with the first character of the line on which
// block was declared.
[operation setCompletionBlock:^{
[self.delegate newDataAvailable];
}];
// Using a block with a C API follows the same alignment and spacing
// rules as with Objective-C.
dispatch_async(fileIOQueue_, ^{
NSString* path = [self sessionFilePath];
if (path) {
// ...
}});
// An example where the parameter wraps and the block declaration fits
// on the same line. Note the spacing of |^(SessionWindow *window) {|// compared to |^{| above.
[[SessionService sharedService]
loadWindowWithCompletionBlock:^(SessionWindow *window) {
if (window) {
[self windowDidLoad:window];
} else {
[self errorLoadingWindow];
} }];
// An example where the parameter wraps and the block declaration does
// not fit on the same line as the name.
[[SessionService sharedService]
loadWindowWithCompletionBlock:
^(SessionWindow *window) {
if (window) {
[self windowDidLoad:window];
} else {
[self errorLoadingWindow];
}
}];
// Large blocks can be declared out-of-line.
void (^largeBlock)(void) = ^{
// ...
};
[operationQueue_ addOperationWithBlock:largeBlock];
命名规范
基本原则
清晰
既清晰又简洁的命名最好,但以清晰为主,不要用单词简写(非常常用除外,苹果列出的可以接受的简写:链接),尽量使用全称。命名应符合OC标准,让人一看就知道是什么意思,不要让人有疑问,使用英文而不是拼音。
一致性
做某件事的代码通常都叫这个名字,比如tag、setStringValue,那你也这么叫。
驼峰原则
大驼峰(UserNameLabel):每个单词首字母大写
小驼峰(userNameLabel):除第一个单词外,其它单词首字母大写
文件命名
Tip
文件名须反映出其实现了什么类 – 包括大小写。
文件的扩展名应该如下:
后缀 | 说明 |
---|---|
.h | C/C++/Objective-C 的头文件 |
.m | Ojbective-C 实现文件 |
.mm | Ojbective-C++ 的实现文件 |
.mm | Ojbective-C++ 的实现文件 |
.cc | 纯 C++ 的实现文件 |
.c | 纯 C 的实现文件 |
- 所有自定义的文件名称以项目工程开头命名,eg:“XP”、“ZJG”、“SZ”
- 针对不同视图控制器,在末尾添加后缀,eg:
- UIViewController 后缀添加“ViewController”
- UIView 后缀添加“View”
类命名
类名(以及类别、协议名)应首字母大写,并以驼峰格式分割单词
- 所有类名称以项目工程开头命名,eg:“XP”、“ZJG”、“SZ”
- 针对不同视图控制器,在末尾添加后缀,eg:
- UIViewController 后缀添加“ViewController”
- UIView 后缀添加“View”
- UIButton 后缀添加“Button”、“Btn”
- UILabel 后缀添加“Label"
类别名
Tip
类别名应该有两三个字母的前缀以表示类别是项目的一部分
或者该类别是通用的。类别名应该包含它所扩展的类的名字。
比如我们要基于 NSString 创建一个用于解析的类别,我们将把类别放在一个名为 GTMNSString+Parsing.h 的文件中。类别本身命名为 GTMStringParsingAdditions (是的,我们知道类别名和文件名不一样,但是这个文件中可能存在多个不同的与解析有关类别)。类别中的方法应该以 gtm_myCategoryMethodOnAString: 为前缀以避免命名冲突,因为 Objective-C 只有一个名字空间。
类名与包含类别名的括号之间,应该以一个空格分隔。
变量命名
Tip
变量名应该以小写字母开头,并使用驼峰格式。
普通变量名
对于静态的属性(int 或指针),尽量为变量起一个描述性的名字。不要担心浪费列宽,因为让新的代码阅读者立即理解你的代码更重要。例如:
- 错误的命名:
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];
类成员变量
- 如果只是单纯的private变量,最好声明在implementation里.
- 如果是类的public属性,就用property写在.h文件里
- 如果自己内部需要setter和getter来实现一些东西,就在.m文件的类目里用property来声明
类成员变量(写在implementation里面),以下划线开头,使用单词全名按顺序拼接方式,属性不需要,系统会自动生成下划线开头的成员变量。
类成员是界面元素的命名
命名方式为变量含义+视图后缀
,采用小驼峰方式书写。
@property (nonatomic, strong) UIViewController *loginViewController;
@property (nonatomic, strong) UIView *loginHeaderView;
@property (nonatomic, strong) UILabel *loginLabel;
@property (nonatomic, strong) UIButton *loginBtn;
常量
常量名(如宏定义、枚举、静态局部变量等)应该以小写字母 k 开头,使用驼峰格式分隔单词,如:kInvalidHandle,kWritePerm。
方法命名
方法名应遵守小驼峰原则,首字母小写,其他单词首字母大写,每个空格分割的名称以动词开头。执行性的方法应该以动词开头,小写字母开头,返回性的方法应该以返回的内容开头,但之前不要加get。
- (void)insertModel:(id)model atIndex:(NSUInteger)atIndex;
- (instancetype)arrayWithArray:(NSArray *)array;
Delegate命名
类的实例必须为回调方法的参数之一, 如
-(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section
回调方法的参数只有类自己的情况,方法名要符合实际含义, 如:
-(NSInteger)numberOfSectionsInTableView:(UITableView*)tableView
以类的名字开头(回调方法存在两个以上参数的情况)以表明此方法是属于哪个类的, 如:
-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
使用did和will通知Delegate已经发生的变化或将要发生的变化, 如:
-(NSIndexPath*)tableView:(UITableView*)tableView willSelectRowAtIndexPath:(NSIndexPath*)indexPath;
-(void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath;
图片资源文件名
- 模块+功能命名法(公共使用:common+功能)。公共模块主要包括统一的背景,导航条,标签,公共的按钮背景,公共的默认图等等;私有模块主要根据app的业务功能模块划分,比如用户中心,消息中心等。
- 单词全拼,或者大家公认无歧义的缩写(如:nav,bg,btn等)
个人中心模块中我的消息按钮示例:personal_btn_my_message
公共模块搜索按钮示例:common_icon_search(或者common_search_icon)
用枚举表示状态、选项、状态码
项目中,用枚举来表示一系列的状态、选项和状态码。例如 iOS SDK 中表示 UICollectionView 滑动方向的枚举定义如下:
typedef NS_ENUM(NSInteger, UICollectionViewScrollDirection) {
UICollectionViewScrollDirectionVertical,
UICollectionViewScrollDirectionHorizontal
};
定义的枚举类型名称应以 2~3 个大写字母开头,而这通常与项目设置的类文件前缀相同,跟随其后的命名应采用驼峰命名法则,命名应准确表述枚举表示的意义,枚举中各个值都应以定义的枚举类型开头,其后跟随各个枚举值对应的状态、选项或者状态码。
对于需要以按位或操作来组合的枚举都应使用 NS_OPTIONS 宏来定义,例如 SDK 中:
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
UIViewAutoresizingFlexibleRightMargin = 1 << 2,
UIViewAutoresizingFlexibleTopMargin = 1 << 3,
UIViewAutoresizingFlexibleHeight = 1 << 4,
UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};
这样定义的选项能够以 按位或操作符 来进行组合,枚举中每个值均可启用或者禁用某一选项,在使用时,可以使用 按位与操作符 来检测是否启用了某一选项,如下:
UIViewAutoresizing resizing = UIViewAutoresizingFlexibleWidth |
UIViewAutoresizingFlexibleHeight;
if (resizing & UIViewAutoresizingFlexibleWidth) {
// UIViewAutoresizingFlexibleWidth is set
}
另外,我们可能使用 switch 语句时,会在最后加上 default 分支,但是若用枚举定义状态机,则最好不要使用 default 分支,因为如果稍后再加了一种状态,那么编译器就会发出警告,提示新加入的状态并未在 switch 分支中处理。假如写上了 default 分支,那么它就会处理这个新状态,从而导致编译器不发出警告,用 NS_ENUM 定义其他枚举类型时也要注意此问题。例如在定义代表 UI 元素样式的枚举时,通常要确保 switch 语句能正确处理所有样式。
编程实践
Init方法
Init方法应该遵循Apple生成代码模板的命名规则,返回类型应该使用instancetype而不是id。
- (instancetype)init {
self = [super init];
if (self) {
// ...
}
return self;
}
类构造方法
当类构造方法被使用时,它应该返回类型是instancetype而不是id。这样确保编译器正确地推断结果类型。
@interface Airplane
+ (instancetype)airplaneWithType:(RWTAirplaneType)type;
@end
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 };
单例模式
单例对象应该使用线程安全模式来创建共享实例。
+ (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。
Xcode工程
物理文件应该与Xcode工程文件保持同步来避免文件扩张。任何Xcode分组的创建应该在本地文件系统体现。代码不仅是根据类型来分组,而且还可以根据功能来分组,这样代码更加清晰。
日志规范
只在debug的时候输出日志, release的时候不输出。减少性能损耗和安全隐患。
在-Prefix.pch(pch全称是“precompiled header”,也就是预编译头文件,该文件里存放的工程中一些不常被修改的代码,比如常用的框架头文件)文件中添加
#ifdef DEBUG
#define DLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
#else
#define DLog(...)
#endif
其中DLog在开发中根据项目进行命名,在实际使用日志的时候,都使用DLog,不使用NSLog。并且该自定义日志可以输出具体的类和行数。实际开发中,只需要用 DLog(...) 就可以在输出需要信息的同时, 还输出所在类、 函数(方法)名以及行数。可以用这种方法很快找到输出所在的位置。
参考:http://zh-google-styleguide.readthedocs.io/en/latest/google-objc-styleguide/contents/
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CodingGuidelines/CodingGuidelines.html#//apple_ref/doc/uid/10000146-SW1
http://www.csdn.net/article/2015-06-01/2824818-objective-c-style-guide/1