For Objective-C , 2018.8.2
Ⅰ.前言
Ⅱ.命名规范
Ⅲ.代码注释规范
Ⅳ.代码格式化规范
Ⅴ.编码规范
Ⅵ.参考资料
Ⅰ.前言
1.需求是暂时的,只有变化才是永恒的,面向变化编程,而不是面向需求编程。
2.不要过分追求技巧,降低程序的可读性。
3.简洁的代码可以让bug
无处藏身。要写出明显没有bug
的代码,而不是没有明显bug
的代码。
4.先把眼前的问题解决掉,解决好,再考虑将来的扩展问题。
Ⅱ.命名规范
1、统一要求
含义清楚,尽量做到不需要注释也能了解其作用,若做不到,就加注释,使用全称,不使用缩写。
2、类名
大驼峰式命名:每个单词的首字母都采用大写字母。
示例:XFHomePageViewController
3、property变量
小驼峰式命名:第一个单词以小写字母开始,后面的单词的首字母全部大写,
属性的关键字推荐按照 原子性,读写,内存管理 的顺序排列,
Block
、NSString
属性应该使用copy
关键字,
禁止使用synthesize
关键词,
可以不暴露在.h
文件中的property
变量,尽量写在.m
文件中,保证.h
文件的简洁。
示例:
typedef void (^ErrorCodeBlock) (id errorCode, NSString *message);
@property (nonatomic, readwrite, strong) UIView *headerView; //注释
@property (nonatomic, readwrite, copy) ErrorCodeBlock errorBlock; //将block拷贝到堆中
@property (nonatomic, readwrite, copy) NSString *userName;
4、私有变量
私有变量放在.m
文件中声明,以_
开头,第一个单词首字母小写,后面的单词的首字母全部大写,
示例:NSString *_somePrivateVariable
ps:一般的,也可以使用property变量
代替私有变量
,这样可能更便于编码时使用点语法 / 懒加载
等;
5.一般变量和对象的命名
给一个对象命名时建议采用 修饰 + 类型 的方式,如果只用修饰命名会引起歧义,比如title
(这个到底是个NSString
还是UILabel
?),同样的,如果只用类型来命名则会缺失作用信息,比如label
(ok,我知道你是个UILabel
,但是用途呢?),So,正确的命名方式为:
titleLabel
//表示标题的label
, 是UILabel
类型
confirmButton
//表示确认的button
,是UIButton
类型
对于BOOL
类型,应加上is
前缀,比如:
- (BOOL)isEqualToString:(NSString *)aString
这样会更加清晰,
如果某方法返回非属性的BOOL
值,那么应根据其功能, 选用has
或is
当前缀,比如:- (BOOL)hasPrefix:(NSString *)aString
ps:如果某个命名已经很明确了, 为了简洁可以省去类型名. 比如scores
, 很明显是个array
了, 就不必命名成scoreArray
了,
6、宏和常量命名
1.对于宏定义的常量,
#define
预处理定义的常量全部大写,单词间用_
分隔
重要:宏定义中如果包含表达式或变量,表达式或变量必须用小括号括起来
2.对于类型常量,
对于局限于某编译单元(实现文件)的常量,以字符k
开头,
例如kAnimationDuration
,且需要以static const
修饰,防止被修改,
对于定义于类头文件的常量,外部可见,则以定义该常量所在类的类名开头,例如XFViewClassAnimationDuration
, 仿照苹果风格,在头文件中进行extern
声明,在实现文件中定义其值,示例:
//宏定义的常量(个别的,如果宏定义个数较多,可以考虑手动左对齐整理)
#define ANIMATION_DURATION 0.3
#define MY_MIN(A, B) ((A)>(B)?(B):(A))
//局部类型常量
static const NSTimeInterval kAnimationDuration = 0.3;
//外部可见类型常量
//XFViewClass.h
extern const NSTimeInterval XFViewClassAnimationDuration;
extern NSString *const XFViewClassStringConstant; //字符串类型
//XFViewClass.m
const NSTimeInterval XFViewClassAnimationDuration = 0.3;
NSString *const XFViewClassStringConstant = @"XFStringConstant";
7、枚举的命名 Enum
Enum
类型的命名与类的命名规则一致,
Enum
中枚举内容的命名需要以该Enum
类型名称开头,
NS_ENUM
定义通用枚举,NS_OPTIONS
定义位移枚举,示例:
//推荐,通用枚举
typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {
UIViewAnimationTransitionNone,
UIViewAnimationTransitionFlipFromLeft,
UIViewAnimationTransitionFlipFromRight,
UIViewAnimationTransitionCurlUp,
UIViewAnimationTransitionCurlDown,
};
//推荐,位移枚举
typedef NS_OPTIONS(NSUInteger, UIControlState) {
UIControlStateNormal = 0,
UIControlStateHighlighted = 1 << 0,
UIControlStateDisabled = 1 << 1,
};
//不推荐用 C 的方式来定义枚举类型
typedef enum : {
CameraModeFront,
CameraModeLeft,
CameraModeRight,
} CameraMode;
8、Delegate
用delegate
做后缀,如<UIScrollViewDelegate>
用optional
修饰可以不实现的方法,用required
修饰必须实现的方法
当你的委托的方法过多, 可以拆分 数据部分 和 其他逻辑 部分, 数据部分用dataSource
做后缀. 如<UITableViewDataSource>
使用did
和will
通知Delegate
已经发生的变化或将要发生的变化。
重要:类的实例必须为回调方法的参数之一
回调方法的参数只有类自己的情况,方法名要符合实际含义
回调方法存在两个以上参数的情况,以类的名字开头,以表明此方法是属于哪个类的,示例:
@protocol UITableViewDataSource<NSObject>
@required
//回调方法存在两个以上参数
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
@optional
//回调方法的参数只有类自己
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView; // Default is 1 if not implemented
@protocol UITableViewDelegate<NSObject, UIScrollViewDelegate>
@optional
//使用`did`和`will`通知`Delegate`
- (nullable NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
9、方法
1.方法名用小驼峰式命名,
2.方法名不要使用new
作为前缀,
3.不要使用and
来连接属性参数,如果方法描述两种独立的行为,使用and
来串接它们,
4.方法实现时,如果参数过长,则令每个参数占用一行,以冒号对齐,
5.一般方法不使用前缀命名,私有方法可以使用统一的前缀来分组和辨识,
6.方法名要与对应的参数名保持高度一致,
7.表示对象行为的方法、执行性的方法应该以动词开头,
8.返回性的方法应该以返回的内容开头,但之前不要加get
,除非是间接返回一个或多个值,
9.可以使用情态动词(动词前面can
、should
、will
等)进一步说明属性意思,但不要使用do
或does
,因为这些助动词没什么实际意义,也不要在动词前使用副词或形容词修饰,
10.(+
开头的)工厂方法 / 类方法,首字母要大写,
11.创建Category
分类方法时,除了满足上述要求外,还应注意考虑尽量避免可能在未来发生的方法名冲突的问题,示例:
//不要使用 and 来连接属性参数
- (int)runModalForDirectory:(NSString *)path file:(NSString *)name types:(NSArray *)fileTypes; //推荐
- (int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes; //反对
//表示对象行为的方法、执行性的方法
- (void)insertModel:(id)model atIndex:(NSUInteger)atIndex;
- (void)selectTabViewItem:(NSTableViewItem *)tableViewItem
//返回性的方法
- (instancetype)arrayWithArray:(NSArray *)array;
//参数过长的情况
- (void)longMethodWith:(NSString *)theFoo
rect:(CGRect)theRect
interval:(CGFloat)theInterval
{
//Implementation
}
//不要加get
- (NSSize)cellSize; //推荐
- (NSSize)getCellSize; //反对
//使用情态动词,不要使用do或does
- (BOOL)canHide; //推荐
- (BOOL)shouldCloseDocument; //推荐
- (BOOL)doesAcceptGlyphInfo; //反对
//工厂方法 / 类方法 首字母大写
+ (XFControlManager *)SharedControlManager;
/**
分类方法(Category),比如创建UIImage的分类方法,两个
1.创建一张纯色图片
2.获取图片中特定点的颜色值
*/
+ (UIImage *)XF_ImageWithColor:(UIColor *)color size:(CGSize)size;
- (UIColor *)xf_ColorAtPixelPoint:(CGPoint)point Alpha:(CGFloat)alpha;
Ⅲ.代码注释规范
优秀的代码大部分是可以自描述的,我们完全可以用代码本身来表达它到底在干什么,而不需要注释的辅助。
但并不是说一定不能写注释,有以下三种情况比较适合写注释:
公共接口(注释要告诉阅读代码的人,当前类能实现什么功能)。
涉及到比较深层专业知识的代码(注释要体现出实现原理和思想)。
容易产生歧义的代码(但是严格来说,容易让人产生歧义的代码是不允许存在的)。
除了上述这三种情况,如果别人只能依靠注释才能读懂你的代码的时候,就要反思代码出现了什么问题。
最后,对于注释的内容,相对于“做了什么”,更应该说明“为什么这么做”。
1、import注释
如果有一个以上的import
语句,就对这些语句进行分组,每个分组的注释是可选的。示例:
// Frameworks
#import <QuartzCore>;
// Models
#import "XFUser.h"
// Views
#import "XFButton.h"
#import "XFUserView.h"
2、属性注释
写在属性之后,用两个空格隔开,示例:
@property (nonatomic, readwrite, strong) UIView *headerView; //注释
3、方法声明注释
一个函数(方法)必须有一个字符串文档来解释,除非它:
1.非公开,私有函数;2.很短;3.显而易见;
而其余的,包括公开接口,重要的方法,分类,以及协议,都应该伴随文档(注释):
以/开始
第二行是总结性的语句
第三行永远是空行
在与第二行开头对齐的位置写剩下的注释,建议这样写:
/This comment serves to demonstrate the format of a doc string.
Note that the summary line is always at most one line long, and after the opening block comment,
and each line of text is preceded by a single space.
*/
方法的注释使用
Xcode
自带注释快捷键:Commond+option+/
,示例:
/**
<#Description#>
//也可以手动删去空行以减少代码行数...
@param tableView <#tableView description#>
@param section <#section description#>
@return <#return value description#>
*/
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
//...
}
4、代码块注释
单行的用// + 空格
开头,多行的采用/* */
注释
5、TODO
使用//TODO:
说明,标记一些未完成的或完成的不尽如人意的地方,示例:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//TODO:增加初始化
return YES;
}
Ⅳ.代码格式化规范
1、指针
*
位置
定义一个对象时,指针*
靠近变量,示例:
NSString *userName;
2、方法的声明和定义
在-
、+
和返回值
之间留一个空格,方法名和第一个参数之间不留空格,示例:
- (void)insertSubview:(UIView *)view atIndex:(NSInteger)index;
3、代码缩进
不要在工程里使用Tab
键,使用空格来进行缩进。在Xcode > Preferences > Text Editing
将 Tab 和自动缩进都设置为 4 个空格
Method
与Method
之间空一行
一元运算符与变量之间没有空格、二元运算符与变量之间必须有空格,示例:
!bValue
fLength = fWidth * 2;
- (void)sampleMethod1;
- (void)sampleMethod2;
4、大括号写法
对于类的method:左括号另起一行写(遵循苹果官方文档),
对于其他使用场景(if,for,while,switch等): 左括号跟在第一行后边,示例:
- (void)sampleMethod
{
BOOL someCondition = YES;
if (someCondition) {
// do something here
}
}
5、对method进行分组(推荐,但不做要求)
使用#pragma mark -对method进行分组,视代码量以及具体情形而定,唯一目的都是增加代码的可读性,示例:
#pragma mark - Life Cycle Methods
- (instancetype)init
- (void)dealloc
- (void)viewWillAppear:(BOOL)animated
- (void)viewDidAppear:(BOOL)animated
- (void)viewWillDisappear:(BOOL)animated
- (void)viewDidDisappear:(BOOL)animated
#pragma mark - Override Methods
#pragma mark - Intial Methods
#pragma mark - Network Methods
#pragma mark - Target Methods
#pragma mark - Public Methods
#pragma mark - Private Methods
#pragma mark - UITableViewDataSource
#pragma mark - UITableViewDelegate
#pragma mark - Lazy Loads
#pragma mark - NSCopying
#pragma mark - NSObject Methods
Ⅴ.编码规范
编码规范简单来说就是为了保证写出来的代码具备三个原则:
可复用
,易维护
,可扩展
,这其实也是面向对象的基本原则;可复用
,简单来说就是不要写重复的代码,有重复的部分要尽量封装起来重用,否则修改文件的时候得满地找相同逻辑的地方;易维护
,就是不要把代码复杂化,不要去写巨复杂逻辑的代码,而是把复杂的逻辑代码拆分开一个个小的模块,这也是Do one thing的概念,每个模块(或者函数)职责要单一,这样的代码会易于维护,也不容易出错;可扩展
,则是要求写代码时要考虑后面的扩展需求,这个涉及到架构层面了,利用对应的设计模式来保证,不再此处赘述;
1.赋值
//Preferred
result = object ? : [self createObject]; //条件赋值
BOOL isAdult = age > 18; //BOOL赋值
//Not preferred
result = object ? object : [self createObject];
BOOL isAdult;
if (age > 18)
{
isAdult = YES;
}
else
{
isAdult = NO;
}
2.
if
条件判断
1.一般的,须列出所有分支(穷举所有的情况),而且每个分支都须给出明确的结果
//Preferred
NSString *hintStr;
if (count < 3) {
hintStr = "Good";
} else {
hintStr = "";
}
//Not preferred
NSString *hintStr;
if (count < 3) {
hintStr = "Good";
}
2.复杂的,条件过多,过长的时候应该换行。条件表达式如果很长,则需要将他们提取出来赋给一个BOOL值,或者抽取出一个方法
//Preferred
if ([self canDeleteJob:job]) { ... }
- (BOOL)canDeleteJob:(Job *)job
{
BOOL invalidJobState = job.JobState == JobState.New
|| job.JobState == JobState.Submitted
|| job.JobState == JobState.Expired;
BOOL invalidJob = job.JobTitle && job.JobTitle.length;
return invalidJobState || invalidJob;
}
//Not preferred
if (job.JobState == JobState.New
|| job.JobState == JobState.Submitted
|| job.JobState == JobState.Expired
|| (job.JobTitle && job.JobTitle.length))
{
//....
}
3.条件语句的判断应该是变量在右,常量在左
//Preferred
if (6 == count) { ... }
if (nil == someObject) { ... }
if (someObject) { ... }
if (!someObject) { ... }
//Not preferred
if (count == 6) { ... }
if (someObject == nil) { ... }
if (someObject == YES) { ... }
if (someObject != nil) { ... }
4.特别地,嵌套判断,不要使用过多的分支,要善于使用return来提前返回错误的情况,把最正确的情况放到最后返回,
//Preferred
if (!user.UserName) return NO;
if (!user.Password) return NO;
if (!user.Email) return NO;
return YES;
//Not preferred
BOOL isValid = NO;
if (user.UserName)
{
if (user.Password)
{
if (user.Email) isValid = YES;
}
}
return isValid;
5.每个分支的实现代码都须被大括号包围
//Preferred:
if (!error) {
return success;
}
//或者
if (!error) return success;
//Not preferred
if (!error)
return success;
3.
for
语句
1.不可在for循环内修改循环变量,防止for循环失去控制
for (int index = 0; index < 10; index++){
...
logicToChange(index);
}
2.避免使用
continue
和break
continue
和break
所描述的是“什么时候不做什么”,所以为了读懂二者所在的代码,我们需要在头脑里将他们取反。
其实最好不要让这两个东西出现,因为我们的代码只要体现出“什么时候做什么”就好了,而且通过适当的方法,是可以将这两个东西消灭掉的;
情形1:如果出现了
continue
:只需要把continue
的条件取反即可,
NSMutableArray *filteredProducts = [NSMutableArray array];
for (NSString *level in products) {
if ([level hasPrefix:@"bad"]) {
continue;
}
[filteredProducts addObject:level];
}
/**
我们可以看到,通过判断字符串里是否含有“bad”这个prefix来过滤掉一些值,
其实我们是可以通过取反,来避免使用continue的,
*/
NSMutableArray *filteredProducts = [NSMutableArray array];
for (NSString *level in products) {
if (![level hasPrefix:@"bad"]) {
[filteredProducts addObject:level];
}
}
情形2:消除
while
里的break
:将break
的条件取反,并合并到主循环里
/**
while (condition1) {
...
if (condition2) {
break;
}
}
在while里的break其实就相当于“不存在”,
既然是不存在的东西就完全可以在最开始的条件语句中将其排除,
取反并合并到主条件:
*/
while (condition1 && !condition2) {
...
}
情形3:在有返回值的方法里消除break:将break转换为return立即返回
- (BOOL)hasBadProductIn:(NSArray<NSString *> *)products {
BOOL result = false;
for (NSString *level in products) {
if ([level hasPrefix:@"bad"]) {
result = true;
break;
}
}
return result;
}
/**
有人喜欢这样做:在有返回值的方法里break之后,再返回某个值,
其实完全可以在break的那一行直接返回,
遇到错误条件直接返回,
*/
- (BOOL)hasBadProductIn:(NSArray<NSString *> *)products {
for (NSString *level in products) {
if ([level hasPrefix:@"bad"]) {
return true;
}
}
return false;
}
4.
Switch
语句
1.每个分支都必须用大括号括起来
switch (integer) {
case 1: {
// ...
break;
}
case 2: {
// ...
break;
}
default:{
// ...
break;
}
}
2.使用枚举类型时,不能有
default
分支, 除了使用枚举类型以外,都必须有default
分支
在Switch语句使用枚举类型的时候,如果使用了default分支,在将来就无法通过编译器来检查新增的枚举类型了
XFLeftMenuTopItemType menuType = XFLeftMenuTopItemMain;
switch (menuType) {
case XFLeftMenuTopItemMain: {
// ...
break;
}
case XFLeftMenuTopItemShows: {
// ...
break;
}
case XFLeftMenuTopItemSchedule: {
// ...
break;
}
}
5.函数
1.一个函数只做一件事(单一原则)
每个函数的职责都应该划分的很明确(就像类一样)
//Preferred
[self dataConfiguration];
[self viewConfiguration];
//Not preferred
- (void)dataConfiguration
{
...
[self viewConfiguration];
}
2.对于有返回值的函数(方法),每一个分支都必须有返回值
//Preferred
- (int)function
{
if (condition1) {
return count1;
} else if (condition2) {
return count2;
} else {
return defaultCount;
}
}
//Not preferred
- (int)function
{
if (condition1) {
return count1;
} else if (condition2) {
return count2;
}
}
3.对输入参数的正确性和有效性进行检查,参数错误立即返回
- (void)functionParam1:(id)param1 Param2:(id)param2
{
if (param1 is unavailable){
return;
}
if (param2 is unavailable){
return;
}
//Do some right thing
}
4.如果在不同的函数内部有相同的功能,应该把相同的功能抽取出来单独作为另一个函数
//Preferred:将a,b函数抽取出来作为单独的函数
- (void)basicConfig {
[self a];
[self b];
}
- (void)logic1 {
[self basicConfig];
[self c];
}
- (void)logic2 {
[self basicConfig];
[self d];
}
//Not preferred
- (void)logic {
[self a];
[self b];
if ([logic1 condition]) {
[self c];
} else {
[self d];
}
}
5.将函数内部比较复杂的逻辑提取出来作为单独的函数
一个函数内的不清晰(逻辑判断比较多,行数较多)的那片代码,往往可以被提取出去,构成一个新的函数,然后在原来的地方调用它这样你就可以使用有意义的函数名来代替注释,增加程序的可读性,举一个发送邮件的例子:
//Preferred
- (void)sendEmail
{
[self openEmailSite];
[ self login];
[self writeEmailTitle:title Content:content
Receiver:receiver Attachment:attachment];
[self send];
}
- (void)writeEmailTitle:(id)title Content:(ID)content
Receiver:(id)receiver Attachment:(id)attachment
{
[self writeTitle:title];
[self writeContent:content];
[self writeReceiver:receiver];
[self addAttachment:attachment];
}
//Not preferred
- (void)sendEmail
{
[self openEmailSite];
[ self login];
[self writeTitle:title];
[self writeContent:content];
[self writeReceiver:receiver];
[self addAttachment:attachment];
[self send];
}
Ⅵ.参考资料
iOS 代码规范
iOS开发总结之代码规范
iOS开发代码规范(通用)
Objective-C开发编码规范
【iOS】命名规范
Ios Code Specification
Apple Coding Guidelines for Cocoa
Google Objective-C Style Guide
iOS团队编程规范