前言
为了利于项目维护以及规范开发,促进成员之间Code Review的效率,故提出以下开发规范,如有更好的建议,欢迎提出。
(一)gitlab分支管理、命名
(二)命名规范
代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。正确的英文拼写和语法可以让阅读者易于理解,避免歧义。
*注意:即使纯拼音命名方式也要避免采用。但alibab、taobao、youku、hangzhou等国际通用的名称,可视同英文.
大驼峰规则:每个单词的首字母大写。例:NameTextField。
小驼峰原则:第一个单词首字母小写,其余都大写。例:nameTextField。
1.项目命名
项目名都遵循大驼峰命名。例如:QuickPushCRM
。
2.类名
类的命名都遵循大驼峰命名。一般是:前缀 + 功能 + 类型。例如:CRM + Login + ViewController
。
在实际开发中,一般都会给工程中所有的类加上属于本工程的前缀。
常用控件类命名类型对照表(下表中前缀为:CRM
,如果用到下表中没有列举出来,请去掉UI首字母
,遵循实际规则即可。)
控件名 | 类型 | 示例 |
---|---|---|
UIViewController | ViewController | CRMBaseViewController |
UView | View | CRMBaseView |
UITableView | TableView | CRMOrderTableView |
UITableViewCell |
Cell |
CRMOrderListCell |
UIButton | Button | CRMSuccessButton |
UILabel | Label | CRMSuccessLabel |
UIImageView |
ImgView |
CRMGoodsImgView |
UITextField | TextField | CRMNameTextField |
UITextView | TextView | CRMSuggestTextView |
其它类相关对照表
功能 | 类型 | 示例 |
---|---|---|
工具类 | Tool | CRMOrderTool |
代理类 | Delegate | CRMOrderListDelegate |
管理类 | Manager | CRMOrderListModel |
模型类 | Model | CRMOrderListModel |
Service类 | Service | CRMOrderService |
布局类 | Layout | CRMHomeLayout |
数据库类 |
DataBase、表名+DBHelper |
CRMFriendDataBase、CRMUserTableDBHelper |
类目 |
XXX+(范围,例如Extension, Additions 或者功能,例如Frame,Nib,Block) |
CRMUIButton+Additions、CRMUIButton+Block |
3. 方法命名规范
所有方法名称禁止以new开始。
所有方法名称禁止使用_开始。(系统会使用_开头命名一些系统私有方法)
内部私有方法需要增加前缀,前缀需要保持唯一性(例如p_)。
Tips: 给私有方法加前缀有2个好处:
- 增加辨识度,提高代码可读性。
- 避免自己的方法无意间覆盖了系统/框架同名的私有方法。
- 如果方法返回接收者的某个属性值,那么请直接使用属性名作为方法名。
正例:
- (CGSize)cellSize;
反例:
- (CGSize)getCellSize;
- 如果方法间接返回一个或多个值,那么请用"getXXX"命名,这种命名只适用于返回多个数据项的情况。
正例:
- (void)getCachedResponseForDataTask:(NSURLSessionDataTask *)dataTask
completionHandler:(void (^) (NSCachedURLResponse * __nullable cachedResponse))completionHandler;
- 方法的每个参数前都必须添加关键字。
正例:
- (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;
- 请不要使用“and”连接接收者属性,尽管and读起来还算顺口,但随着你创建的方法参数的增加,这将会带来一系列的问题。
正例:
- (int)runModalForDirectory:(NSString *)path file:(NSString *) name types:(NSArray *)fileTypes;
反例:
- (int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes;
- 如果方法描述了两个独立的动作,则可以使用"and"连接起来。
正例:
- (BOOL)openFile:(NSString *)fullPath withApplication:(NSString *)appName andDeactivate:(BOOL)flag;
4. Protocol命名规范
- Protocol中的方法命名以触发消息的对象名开头,省略类名前缀并首字母小写,如果它没有关联任何类则可以忽略这个规则。
正例:
- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row;
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename;
- 除非Protocol方法只有一个参数,否则冒号需紧跟在类名后面。
正例:
- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row;
- (BOOL)applicationOpenUntitledFile:(NSApplication *)sender;
5. Category命名规范
- 分类命名也要和类命名一样添加前缀。
正例:
UIView (YYAdd)
反例:
UIView (Add)
分类中声明的方法名都要加上前缀。
Category中尽量不要声明属性,能挪尽量挪到主类中声明。
Tips: 尽管从技术上来讲可以在分类中声明属性,但是这么做需要格外小心,
因为它很容易出现内存上或其他一些问题,而且一旦出现问题很难排查。
- 如果一个类比较复杂,那么建议使用分类组织代码(可以参考系统的UIView)。
6. Notification命名规范
- Notification的命名风格由"类名前缀" + “通知事件名称” + "Notification"3个部分组成。
正例:
UIApplicationDidBecomeActiveNotification
UIApplication表示该通知属于谁,DidBecomeActive表示该通知的作用,Notification表示它是一个通知。
- 如果一个类声明了delegate属性,通常情况下,这个类的delegate对象应该可以通过实现的delegate方法收到大部分通知消息。
Tips:
例如applicationDidBecomeActive:代理方法和NSApplicationDidBecomeActiveNotification通知(这其实也符合命名规范的基本原则"一致性")。
7. 常量命名规范
- 如果常量局限于某"编译单元"之内,通常在前面加小写字母k作为前缀,若常量在全局可见,通常以类名作为前缀,然后采用首字母大写的驼峰式命令风格。
正例:
// 局部可见
const CGFloat kAnimationDuration = 2.0;
// 全局可见
const CGFloat UIActivityIndicatorViewAnimationDuration = 2.0;
8.变量和方法
变量和方法的命名都遵循小驼峰命名。例如:textVariableStr
, - (void)textAction
响应事件。
方法命名对照表(方法多为动词或动名词)
功能 | 示例 |
---|---|
- (id)initXX | 初始化相关方法,使用init为前缀标识,如初始化布局- (id)initView |
- (BOOL)isXX | 方法返回值为boolean型的请使用is前缀标识 |
- (UIView *)getXX | 返回某个值的方法,使用get为前缀标识 |
- (void)setXX | 设置某个属性值或者相关数据 |
- (void)updateXX | 更新数据 |
- (void)saveXX | 保存数据 |
- (void)drawXX | 绘制相关,使用draw前缀标识 |
- (void)clearXX | 清除数据 |
- (void)XXXAction |
响应事件,使用Action为后缀标识 |
- (void)loadData |
加载数据(一般情况下VC中都会有这个方法) |
- (void)loadMoreData |
加载更多数据 |
- (void)setupUI |
加载布局(一般情况下VC中都会有这个方法) |
9. 资源文件命名
全部小写,采用下划线命名法,加前缀区分。所有的资源文件都需要加上工程前缀(小写形式)。
命名模式:可加后缀_smal
l表示小图,_big
表示大图,逻辑名称可由多个单词加下划线组成,采用以下规则:
用途_模块名_逻辑名称
用途_模块名_颜色
用途_逻辑名称
用途_颜色
说明 | 前缀(工程前缀示例CRM) | 示例 |
---|---|---|
按钮相关 | crm_btn_ | crm_btn_home_normal、crm_btn_red,crm_btn_red_big |
背景相关 | crm_btn_ | crm_bg_home_header、crm_bg_main |
图标相关 | crm_ic_ | crm_ic_home_location、crm_bg_input |
分割线相关 | crm_div_ | crm_ic_home_location、crm_bg_input |
默认相关 | crm_def_ | crm_ic_home_location、crm_bg_input |
10. 文件夹命名
创建文件夹最好创建实体文件夹,找到工程目录,创建相应文件夹并拖入工程。文件夹命名使用相应模块结构分层的英文,首字母要大写。例:Model
,View
,Controller
,Tool
,Other
,Service
等等。
(三)编码规范
- 所有的方法之间空一行。
- 所有的代码块之间空一行,删除多余的注释。
- 局部使用的常量、静态变量声明在
@interface
之前。 @property
同一类型的声明放在一块,不同类型的声明用2行空格隔开。
正例:
@interface MineViewController ()
@property (nonatomic, weak) UIView *headView;
@property (nonatomic, weak) UITableView *tableView;
我是换行符,请忽略
@property (nonatomic, copy) NSArray *dataSourceArray;
- 不同逻辑、不同语义、不同业务的代码之间插入一个空行分隔开以提升可读性。
正例:
[self createSubviews];
[self createTableview];
[self netRequest];
- 所有自定义的方法需要给出注释。
- 尽量使用懒加载,在控制器分类时有提及和要求,其它自定义类按照控制器格式分类,没有的分类不写即可。
- 代码后的’{‘不需要独占一行,包括方法之后,
if
,switch
等。 - 如果有使用到
CF(Core Foundation)
等框架时,或者是在iOS10以下系统使用通知和KVO时,切记在dealloc方法中释放对象以及移除通知和监听。 - 在
dealloc
方法内禁止将self
传递出去,如果self
被retain
,到下个runloop
周期再释放则会多次释放导致crash
。
反例:
- (void)dealloc {
[self unsafeMethod:self];
}
- 当方法可能会提前
return
时,需要要注意对象的释放问题,避免内存泄漏。
反例:
CFArrayRef arrayRef = (__bridge CFArrayRef)array;
if (x == YES) return;
CFRelease(arrayRef);
以上代码如果x等于YES的话那么arrayRef对象就会内存泄漏。
- 当使用
@try
处理异常时,需要要注意对象的释放问题,避免内存泄漏。
反例:
@try {
CFArrayRef arrayRef = (__bridge CFArrayRef)array;
do some thing……
CFRelease(arrayRef);
} @catch (NSException *exception) {
}
以上代码如果do some thing……出现异常的话那么arrayRef就会出现内存泄漏。
- 必须要统一的要求,属性的定义请按照下图
property
之后,空一格,括号之后空一格,写上类名,空一格之后跟上*
和属性名。
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) DeliveryModel *delivery;
@property (nonatomic, strong) DeliveryLookAdapter *lookAdapter;
@property (nonatomic, strong) DeliveryLookAPIManager *lookManager;
- 使用一目运算符时左右两边不能有空格。
正例:
i++、++i、
反例:
i ++、++ i
- 使用二目、三目运算符时左右两边必须有且仅有一个空格。
正例:
1 + 2
反例:
1+2
- 采用4个空格缩进,如果要使用Tab字符,请将1个Tab设置成4个空格。
- 单行字符数限制不超过150个,超出需要换行(空格可以除外),换行时遵循如下原则:
Tips:
1. 第二行相对第一行缩进4个空格,从第三行起不再继续缩进。
2. 运算符与下文一起换行。
3. 方法调用的点符号与下文一起换行。
正例:
- (void)setImageWithURL:(nullable NSURL *)imageURL
placeholder:(nullable UIImage *)placeholder
options:(YYWebImageOptions)options
progress:(nullable YYWebImageProgressBlock)progress
ransform:(nullable YYWebImageTransformBlock)transform
completion:(nullable YYWebImageCompletionBlock)completion;
- 遵循一般代码规范,多模仿苹果
API
。 - 删除不用的代码。
- 如果有方法一直不会用到,请删除(除工具类)。
- 没有执行任何业务逻辑的方法,请删除或给予注释,删除多余的资。源或文件,添加必要的注释。
- 比较大的代码块需要给出注释。
UIViewController请按照如下分类
#pragma mark - Life Cycle
#pragma mark - Private
#pragma mark - Public
#pragma mark - UITableViewDelegate && UITableViewDataSource
#pragma mark - CustomDelegate
#pragma mark - Getters and Setters
#pragma mark - Lazy Loading
注意:所有视图或者对象的创建请尽量使用懒加载,调用的时候全部使用self.textBtn这样的方式。如果是确定的不可变数组、字典,可直接给定数组中的元素。(getters and setters分类中,懒加载可出现_调用对象,其它情况请遵循self.调用原则)
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong)UIButton * textBtn;
@end
@implementation ViewController
#pragma mark - life cycle
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.textBtn];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - private
#pragma mark - event response
#pragma mark - UITableViewDelegate && UITableViewDataSource
//(代理顺序往下排列)
#pragma mark - CTAPIManagerCallBackDelegate
#pragma mark - getters and setters
- (UIButton *)textBtn
{
if (_textBtn == nil) {
_textBtn = [UIButton buttonWithType:UIButtonTypeCustom];
_textBtn.frame = CGRectMake(300, 250, 100, 100);
_textBtn.backgroundColor = [UIColor yellowColor];
_textBtn.titleLabel.text = @"text";
[_textBtn addTarget:self action:@selector(text) forControlEvents:UIControlEventTouchUpInside];
}
return _textBtn;
}
#pragma mark - Lazy Loading
@end
(四) 注释规范
为了减少他人阅读你代码的痛苦值,请在关键地方做好注释。
1.类注释
//
// CRMNewToNetViewController.h
// QuickPushCRM
//
// Created by susu on 2021/6/18.
// Copyright © 2021 方俊. All rights reserved.
//
该注释是自动生成的,在xcode
中设置即可。Created by
电脑用户名on
创建该文件的时间。Copyright 2021
后面的名字和邮箱是自己填写和设置的。具体可在xcode
工程,Project Document
中设置。这样便可在每次新建类的时候自动加上该头注释。
2.方法注释
方法注释,方法外部统一用
option + command + /
,方法内部统一用//
注释。
/**
测试
*/
- (void)text
{
//测试按钮事件响应
}
3.模型注释
每个model
中的,包含的每个属性,都必须要写上相对应的注释,用///
注释。阅读者一看这个model,就清楚知道model
中的每个字段代表的意思,用来做什么事情的。
@interface DeliveryModel : NSObject
///提货劵所在商圈id
@property (nonatomic, assign) long long mallId;
///商圈全称
@property (nonatomic, copy) NSString *mallFullName;
///商圈简称
@property (nonatomic, copy) NSString *mallShortName;
///提货劵号
@property (nonatomic, copy) NSString *credentialsCode;
///总金额
@property (nonatomic, assign) NSInteger totalAmount;
///提货劵所在店铺id
@property (nonatomic, assign) long long storeId;
///货劵所在店铺名称
@property (nonatomic, copy) NSString *storeName;
///提货劵id
@property (nonatomic, strong) NSNumber *credentialsId;
///状态:0:未提货、1:已提货、2:已分享、3:已退款
@property (nonatomic, assign) NSInteger state;
///提货商品(以下为提货商品参数)
@property (nonatomic, strong) NSArray *goodsList;
///二维码
@property (nonatomic, copy) NSString *qrCode;
///商品总个数
@property (nonatomic, assign) NSInteger goodsCount;
@end
如果不是model的属性,是其它类属性,需要注释,请按照model属性注释方式。
对于代码注释需谨慎,代码被注释一般有2种可能,1) 后续会恢复此段代码逻辑; 2) 永久不用;对于第1种情况需添加相应注释,如果没有注释信息难以知晓注释动机,后者建议直接删除。如果有需要可以通过代码仓库查阅历史代码。
使用特殊注释标记时,请注明标记人和标记时间,注意及时处理这些标记。
正例:
/**
* @brief 简要描述
* @author 标明开发该类模块的作者
*/
// FIXME: 有bug,需要修改
- (void)testFunction;
(五) 版本规范
采用A.B.C 三位数字命名,比如:1.0.2,当有版本更新的时候,依据下面的情况来确定版本号规范。
版本号 | 说明 | 示例 |
---|---|---|
A.b.c | 属于重大更新内容 | 1.0.2 -> 2.00 |
a.B.c | 属于小部分更新内容 | 1.0.2 -> 1.2.2 |
a.b.C | 属于补丁更新内容 | 1.0.2 -> 1.0.4 |
(六)第三方库规范
希望Team能用时下较新的技术,对开源库的选取,一般都需要选择比较稳定的版本,作者在维护的项目,要考虑作者对issue的解决,以及开发者的知名度等各方面。选取之后,一定的封装是必要的。
项目使用cocoapods统一管理开源第三库文件,不需要手动导入和手动添加依赖库。如果第三方不支持cocoapods,可手动导入工程。
(七)其它规范
- 提取方法,去除重复代码。对于必要的工具类抽取也很重要,这在以后的项目中是可以重用的。
- 尽可能的使用局部变量
- 尽量减少对变量的重复计算。
- 尽量在合适的场合使用单例。使用单例可以减轻加载的负担,缩短加载的时间,提高加载效率。但并不是所有的地方都适用于单例,简单来说单例主要适用于以下三个方面:
1. 控制资源的使用,通过线程同步来控制资源的并发访问。
2. 控制实例的产生,以达到节约资源的目的。
3. 控制数据的共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信。 - 最后不要忘了检测内存泄漏。可使用Instruments分析内存。