[TOC]
1.引言
1.1目的
本文档的目标用户为iOS开发人者,提供在iOS项目开发过程中的代码编写规范,以提高代码的可读性、可维护性和可扩展性。
1.2术语与缩略语
-
方法
指object-c/swift中的class的实例方法(method)或类方法。
-
函数
object-c中允许与c语言混编,这里指独立的c函数。
-
#pragma mark
object-c中的编译指令,导航中分隔相关属性或方法,便于查找
-
//MARK:
swift中的编译指令,导航中分隔相关属性或方法,便于查找
1.3参考资料
《Coding Guidelines for Cocoa》,Apple,https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CodingGuidelines/CodingGuidelines.html
《Objective-c-style-guide》,New York Times, Robots & Pencils, https://github.com/raywenderlich/objective-c-style-guide
2.命名规范
在进行项目开发工作时,我们经常需要定义一些新的类名、协议名、变量名、方法签名、函数签名、常量和枚举等,在对这些元素进行命名的时候要尽量做到看词达意,力求能够精确描述其代表的含义或提供的功能,杜绝命名的二义性和随意性。
2.1命名空间-前缀
object-c/swift并没有提供命名空间机制,为了防止命名冲突,可以在类名、协议名、常量名前加一个前缀防止与系统类库或第三方类库之间的命名冲突。前缀一般为大写的二个或三个字母,可以为公司或项目名称缩写的大写形式。
一个标准的带前缀的命名格式形如:PREFIXNameType,PREFIX为前缀,Name为命名,Type为可选的类型名称后缀,表示自定义类继承于Cocoa的某个类。例如NS为苹果前公司NeXTSTEP的缩写,Cocoa库中的好多类用它做前缀。再比如类名AECRegistrationViewController,AEC为前缀,Registration为类名,表示应用注册功能的控制器,ViewController为后缀,表示该自定义类继承自UIViewController。
不要在方法名中使用前缀,因为方法是属于类的命名空间之下。同理,结构体的字段也不应使用前缀。
注:可以使用公司英文缩写作为前缀,比如:USMARTLoginViewController
2.2类名与协议名
- 类名和协议名采用大骆驼拼写法,即每个单词的第一个字母都需要大写。
- 类名和协议名使用前缀。其中AppDelegate类命名方法不应采用此规则,仍保持原有命名方式。
- 类名和协议名中应使用一个名词来描述该类或协议的主要功能或作用。
- 协议名后面加Protocol或Delegate作为后缀。
- 继承于UIViewController的子类的命名添加后缀ViewController。
- 继承于UIView的子类的命名添加后缀View。
- 用于业务数据处理的类添加后缀Model。
- Delegate实例变量统一命名为delegate
2.3变量名
- 变量名使用小骆驼拼写法,即第一个单词首字母小写,其余单词首字母大写。第一单词是常用缩写除外。
- 变量名要有意义,要能表达其所代表的含义。
- 避免过多使用全局变量。
- 变量的命名应当遵循apple设计初衷,尽量完整,少用缩写
- 对于一些不能够通变量名来表征其类型的,需要通过后缀类型的方式让使用者知道其类型,如下:
oc | swift | 例子 |
---|---|---|
NSArrary | Array | userInformationArray |
NSDictionary | Dictionary | userInformationDictionary |
注:Objc和Swift通用
- 对于常用基本数据类型应适当添加后缀或前缀,如下表:
类型 | 单精度浮点 | 全局 | Static |
---|---|---|---|
命名规范 | f后缀 | g_前缀 | s_前缀 |
2.4常量名
遵循小骆驼拼写法,即首字母小写,其它单词首字母大写
2.5枚举
枚举类型命名和其成员均使用大写,以Objc为例如下:
注:swif灵活使用,此例子仅适用于Objc
2.6方法的命名规范
- 使用小骆驼拼写法,首字母小写,其余单词首字母大写
- 方法名命名要有意义,最好能够表示方法所提供的功能。
- 如果方法提供操作功能,则方法名以动词开头。
- 对于objc如果方法名需要输入参数,则方法名的最后一个关键字应当描述参数。Swfit则可以忽略这项
注:因为objc的语法特性,要求函数名和参数描述基本上要是完整的一句话描述。所以方法名的最后一个关键字应当描述紧跟其后的第一个参数。但是swift因为语法不同则不在此过多要求。
2.7图片资源命名
图片命名名全部小写。命名可以分为三段式且中间应当以_来分割:
1.功能名称
2.控件类型
3.图片更详细的使用目的描述,如颜色位置,及放在描述最末的图片状态等
例如:
share_button_selected.png
share_button_selected@2x.png
share_button_selected@3x.png
注:后续同UI商定具体的命名规则。
2.8字符串资源命名
字符串资源的命名全部小写。命名可以分为主要的四个部分并且以_来分隔:
1.功能模块名称
2.功能性控件名称
3.系统控件名称
4.显示文字英文标示
例如:
share_username_textfield_username = “Username”
*注:对于通用的字符串,应当使用common来做前缀。例如:common_ok = “Ok”
对于一些变量名字已经包含控件名称的,定义时候可以去重处理.比如,变量名字为usernameTextField.那么定义字符串的时候就不用重复再写textfield.*
2.9保留字
保留字又名关键字,为iOS系统专用,禁止用作变量名,类名,方法名。
3.代码规范
3.1排版格式
3.1.1基本格式排版
类文件中代码排布应当遵循整齐统一的原则,不能散乱。
1.ViewController文件排版
2.生命周期
3.IBActions方法
4.系统控件delegate方法,例如键盘相应,TableView delegate方法等
5.系统控件datasource方法
6.自定义delegate 和 notification方法
7.其它业务逻辑方法
8.View 文件排版
9.初始化方法
10.详悉布局方法
11.同viewcontroller通讯方法
注:所有的实例变量声明必须放在文件开头部分
3.1.2详细格式排版
1.文件说明与头文件包含(import)之间空1行
2.头文件包含(import)与@class(如果有声明)、@interface和@protocol之间各空1行
3.如果声明属性和方法,先声明属性,后声明方法。属性声明与方法声明之间空1行,两者内部如果需要分类区别,各类别之间空1行。
4.属性声明和方法声明整体与@interface/@protocol和@end之间各空1行
5.方法实现与@implementation和@end之间各空1行。
6.方法实现之间空1行
7.#pragma mark或//MARK:与相关关系第一个方法之间不应有空行,而于其上方方法实现之间空1行
8.方法声明和实现中-/+与返回值之间空一格
9.方法实现、if语句、switch语句、while语句和do-while语句中“{”在第一行以空格分开,“}”在另一行作为结束。
10.方法实现第一行代码和最后一行代码不应当和“{}”作用域符号间有空行
11.一元运算符如&!与操作数之间不能有空格,两元运算符与两个操作数之间以空格分隔。
12.三目运算符中每个运算符都应当和操作数中间有一个空格。
13.关键字与其后面的表达式用空格分隔。
3.2变量
3.2.1全局变量
尽可能少的使用全局变量
3.2.2实例变量
-
IBOutlet类型实例变量
IBOutlet属性实例变量名字需要和在xib或者storyboard中定义的名称一直,以方便维护。
swift中定义的IBOutlet属性实例变量可以直接以!来声明
-
一般类型实例变量
swift中自定义变量如果不明确其值,不能强制使用!来声明,应当统一以?来声明。在之后的访问中需要使用有效性检查来进行操作。
注:声明属性代替定义变量,方便系统内存管理。
3.3属性
- 除非需要,不要手动去synthesize属性
- 对于引用类型的属性声明,正确使用weak/strong特性,方便系统内存管理:代理、IBOutlet使用weak,NSString一般使用copy,其它情况一般使用strong。
- 如果属性需要只读,则在声明时候添加readOlny特性。
- objc私有属性必须放到匿名扩展类中。
- swift私有属性和方法使用private关键字。
3.4封装
- objc中需要暴露给外的接口和属性,统一放到.h头文件中
- objc中的私有属性和方法需要在.m文件中定义单独的私有作用域。
- Swift中的私有属性和方法需要使用private关键字
3.5拒绝硬编码
在编码过程中应该杜绝硬编码,即不直接使用字符串或数字在表达式里面。而改用能表达其意义或作用的局部变量或系统配置的方式作为代替,以提高代码的可读性和可维护性。常见硬编码形式:
- 数值类型的硬编码
- RGB色值的硬编码
- 字符串显示定义的硬编码
- URL的硬编码
- 网络状态码的硬编码
- 控件Frame的硬编码
下图以objc为例:
#define DEFAULT_AGE 10使用宏定义
RGB
Button.backgroundColor = [UIColor colorWithRed:38.0f/255.0f green:40.0f/255.0f blue:90.0f/255.0f]
Button.backgroundColor = UICOLORFROMRGB(38.0f, 40.0f, 90.0f, 1.0f)
#define UICOLORFROMRGB(r, g, b, a) [UIColor colorWithRed:r/255.0f..]
使用宏定义
字符串
Label.text = “Login”
NSLocalizedStringFromTable (@”str_name”, @”StringTableName”, @”Comment”)
自定义字符串资源文件,对字符串资源统一管理
URL
loginRequest.baseURL = “https://……..”
loginRequest.baseURL = [URLManager getloginBaseUrl]
应当定义单独的url管理工具类,针对各个需要的功能分别创建get方法。而domain域的baseurl需要在单独的文件中区分不同的编译模式来宏定义。比如可以分为DEBUG, TEST, PRODUCT来分别配置
网络状态码
Case: 300 {
……..
}
Case: NETWORK_REDIRECTION
同样需要以宏的形式定义
控件frame的设置
UIButton* button = [[UIButton alloc]initWithFrame:CGRectMake(30.0f, 50.0f, 120.0f, 80.0f)];
使用相对Frame或者约束
3.6图片资源文件
在设置图片资源的时候,文件名字和后缀必须都要写全。例如,xxx.png xxx.gif 因为有可能同样名称的图片资源,如果不添加后缀,往往引用不到指定的图片资源。
3.7方法
3.7.1目标
根据单一职责原理,一个方法应当只做特定的操作或完成特定的任务,与方法名保持统一。同时,在方法体内不要出现太多层级的嵌套,如if、switch、for循环等之间的相互嵌套。
3.7.2方法组织
这一章节的目的在于更加快速定位方法集合并使得代码组织更加清晰
3.7.2.1ViewController方法组织
以objc为例,建议使用如下组织方式对方法进行分类,方便定位和阅读:
1.生命周期 #pragma mark - life cycle
2.IBActions方法 #pragma mark – IBActions
3.系统控件Delegate方法, 以tableview为例 #pragma mark – UITableViewDelegate
4.系统控件DataSource方法,以tableview为例 #pragma mark – UITextFieldDataSource
5.自定义public方法 #pragma mark – Public
6.自定义private方法 #pragma mark – Private
#pragma mark - life cycle
IBActions
#pragma mark – IBActions
系统控件Delegate方法
#pragma mark – UITableViewDelegate
系统控件DataSource方
#pragma mark – UITextFieldDataSource
自定义public方法
#pragma mark – Public
自定义private方法
#pragma mark – Private
3.7.2.2View方法组织
1.初始化方法 #pragma mark – Init
2.布局方法 #pragma mark – Layout
3.展示数据读取方法 #pragma mark – Datas parse
4.和viewcontroller通讯Delegate #pragma mark – Delegate
组织模块
组织命名
初始化方法
#pragma mark – Init
布局方法
#pragma mark – Layout
展示数据读取方法
#pragma mark – Datas parse
Delegate
#pragma mark – Delegate
3.7.2.3Model方法组织
1.初始化方法 #pragma mark – Init
2.数据解析方法 #pragma mark – Datas parse
组织模块
组织命名
初始化方法
#pragma mark – Init
数据解析方法
#pragma mark – Datas parse
3.7.3通用方法规范
- 方法体与“{”,与“}”间不需要空行。
- 方法体最大行数为100行,超长情况按功能进行拆分。
- 如果方法体内嵌套if、switch、for或while语句,嵌套层数不能超过四层。层级过深的时候考虑拆分。
- 黄金路径:在使用条件分支时,当方法体不满足继续执行条件时,使用return语句结束函数。可以多次使用return。
- 对方法入口参数进行有效性检查。
3.8控制语句
控制语句包括if语句、switch语句、for循环语句、while语句和do-while语句。
- 控制语句关键字与其后表达式用一个空格隔开。
- 控制语句分支中的函数体必须用大括号括起来,一行代码也需要。
3.8.1If语句中的条件运算符
如果遇到多个条件运算符的情况,需要以运算符为临界换行输出。
3.9单例
单例的实例对象需要采用线程安全的方式还创建,一般使用GCD的方式,例如:
+ (instancetype)sharedInstance {
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
3.10函数
函数的行数不能超过50行,纵向嵌套层数不能超过三层。
3.11CGRect函数
当访问CGRect的属性x、y、width、height的时候使用CGGeometry的函数,不要直接访问其结构体。例如:
避免使用
CGFloat x = frame.origin.x;
CGFloat x = CGRectGetMinX(frame);
CGFloat y = frame.origin.y;
CGFloat y = CGRectGetMinY(frame);
CGFloat width = frame.size.width;
CGFloat width = CGRectGetWidth(frame);
CGFloat height = frame.size.height;
CGFloat height = CGRectGetHeight(frame);
CGRect frame = (CGRect){ .origin = CGPointZero, .size = frame.size };
CGRect frame = CGRectMake(0.0, 0.0, width, height);
3.12注释
3.12.1注释方法
单行注释使用“//”,多行则使用“/”与“/”组合
3.12.2注释规则
1.注释要与被注释的内容保持一致,需要及时更新。
2.尽量做到代码自解释,少使用注释,编写framework或静态库除外。
3.注释语言应当使用英文。
4.代码对齐
5.代码必须做对齐处理。
4.工程目录组织结构
4.1主目录结构
主要遵循MVC设计模式
ViewControllers
Widgets
Models
4.1.1目录结构功能划分
- ViewControllers
视图控制器,主要处理view和model的通讯。
- Widgets
视图布局,主要处理视图的具体排布和读取展示数据已经向controller发送触发消息。应当根据不同的功能模块分别创建Group,并且以功能模块来命名。
- Models
数据层,应当仅包含各功能模块业务逻辑数据处理。Models层应当根据不同的功能模块分别创建Group,并且以功能模块来命名。
4.1.2各目录组织功能涵盖
-
ViewControllers
1.生命周期
2.调用对应widgets初始化接口布局view
3.系统控件delegate和datasource方法
4.调用网络request实例初始化网络请求必须数据
5.调用Model层网络请求业务处理逻辑来发起请求。
6.接收网络请求处理状态,分别以succed和failed方式来响应。
7.将Model层回传数据传递给view以供展示
-
Widgets
1.布局控件
2.读取需展示内容
3.向控制器发送事件trigger命令
-
Models
1.网络数据的请求数据抽象
2.网络数据的response数据抽象解析
3.同控制器通讯传递数据
4.2扩展目录结构
1.Helpers
2.Resource
3.ThirdParty
4.Externsions
5.Category
4.2.1目录结构功能划分
-
Helpers
工具类,包含例如字符串,字典类型的有效性检查等。
-
Resource
字符串资源文件和图片,音频等文件的存放。
-
ThirdParty
第三方库,且未有通过cocoapods来加载。
-
Externsions
类的扩展,比如自定义string的方法等。
-
Category
类别,为需要的类添加新的方法。
4.2.2各目录组织功能涵盖
-
Helpers
1.数据库管理类,包含基本的增,删,改,查等功能。
2.DateFormatter工具类,包含字符串和Date类型的相互转换,Date显示格式的动态完成等功能。
3.Objc中字符串,字典等常用collection类型的有效性检查。
4.UserDefault的工具类,包含设置,更新,清空等功能。
5.Keychain的工具类,多见于用户名和密码的存储。常用接口应提供设置,读取,更新和清空功能。
6.URL的管理类,为各个功能模块提供所需的URL get 方法。
7.全局性的宏定义类,包含不同编译模式下的domain域Base URL设置;颜色值宏定义方法;网络状态码的宏定义等。
-
Resource
存放图片,音视频,字符串等资源文件
-
ThirdParty
存放除去cocoapods外的其它第三方库
5.工程设置规范
本章节主要涵盖工程设置方面的规范,包含编译模式的设定,文件路径的设置等。均以objc为例,swift适用。
5.1多个编译模式设置
5.1.1目标
在开发过程中,往往存在不同环境下的网络请求URL访问和日志过滤需求。那么就需要为工程配置相应的编译模式切换条件。
5.1.2常见使用场景
预编译宏
使用场景
- DEBUG
调试版本下对应的网络环境URL的访问
日志信息的打印
- TEST
送测版本下对应的网络环境URL的访问
日志信息的过滤
- PRODUCTION
最终发布版本下对应的网络环境URL的访问
日志信息的过滤
5.2自定义日志信息
自定义日志文件而不直接使用系统日志文件的意义在于可以更好地做到日志输出信息的自定义和日志过滤。
1.pch文件中简单封装,多见于输出类名,方法名和行号的形式。
2.日志文件库,扩展功能更加丰富,例如除去常见的日志输出信息,还可以显示日志输出者,对日志等级的详细划分(warning, error…),时间戳等。
5.3路径设置
5.3.1目标
在开发过程中,会用到第三方Framework等的使用需要。往往需要在工程中配置相应的搜索路径。常见的路径设置无外乎绝对路径和相对路径。使用绝对路径虽然同样可以达到预想的目的,但是在实际开发过程中,尤其是多人协作的过程中,使用绝对路径往往会导致编译失败的常见问题。因此,使用相对路径是需要注意的地方。
5.3.2常用相对路径设置
名称
代表路径
实例
$(PROJECT_DIR)
当前项目完整路径
/Users/delta-aec-app/Desktop/…
$(SRCROOT)
项目根目录即 .xcodeproj文件所在的路径
N/A
$(BUILT_PRODUCTS_DIR)
Build成功后的最终产品路径
Build/xxx
$(CURRENT_PROJECT_VERSION)
当前项目版本号
N/A
6.版本管理
6.1目标
这一章节主要囊括了开发过程中版本管理方面的注意事项。目的在于更好地维护开发版本和后期维护的顺利进行。
6.2版本号
Version一般有2段或者3段式, 如:1.0, 1.0.0
Ios规范版本号形式为三位形式,即:主版本号+副版本号+发布号
-
基础版本号
版本号的初始值应当为v1.0.0
-
版本号管理规则
分段版本号
-
变更规则
主版本号 (Major Version)
产品的主体构件进行重大修改,主版本号加1
产品的主体构件之间的接口协议重大修改,主版本号加1
-
副版本号(Minor version)
-
主版本号变更时,副版本号置0;
-
数据结构变更(新增或修改注释含义的情况除外),副版本号加1;
-
若副版本号累加至超过20时,采用主版本号进位制,主版本号加1,副版本号重新置0。
-
有新增的小功能模块时,副版本号加1;
-
-
发布号(Release)
平时的迭代送测版本,叠加1;
- 主版本号或副版本号变更时,Release号置0;
- 若发布的版本无数据结构变更,则Release号加1。
6.3版本送测
6.3.1打包版本
送测打包版本应当首先设置为TEST版本,即不应当开启日志功能,即不能为DEBUG版本。
6.3.2打包工具
尽量不要使用手动大包的形式,应当使用诸如TestFlight工具来编译发布。这样很大程度上会提升送测的效率,使得送测点更加清晰。
6.4代码提交
代码提交习惯
- 新的开发起始,要首先Update工程。
- 对于冲突项,应和相关人一起查看Merge代码。
提交代码时如果有会引发多人协作编译冲突类的文件,需要在提交初期做忽略处理。例如Cocoapods的提交,一般需要忽略CocoaPods的两个文件,即Pods、Podfile.lock,但不能忽略*.a。当其他人员Checkout下来后,再进行pod install操作,就可以正常使用了
- Commit信息要尽量对修改点进行描述,分两类:
- 普通修改:说明修改点。
- Issue修改:应当遵循 #Issue number + 换行 + 补充描述的格式。
7.补充
7.1第三方库的使用合法性
对于工程中可能出现大量使用第三方库的情况,但是需要遵循一定的规则,才能做到合理,合法的使用。通常可以在其Git代码首页查看license或者readme中查找。
7.1.1开源性
需要保证代码的开源性,如果有任何对商用需要收费限制甚至明确说明非开源,就不能使用。
7.1.2可维护性
使用的第三方库尽可能用当下的主流程序,好处在于相关资料较易查找,并且出现问题会有新版本的不断升级。
7.1.3授权协议
第三方库必须有正规的授权协议,常见的协议有:
- 协议名称
- 介绍
- 权利
- Apache (Apache License)
7.1.3.1Apache许可协议
- 永久权利一旦被授权,永久拥有。
- 全球范围的权利在一个国家获得授权,适用于所有国家。
- 授权免费,且无版税前期,后期均无任何费用。
- 授权无排他性任何人都可以获得授权
- 授权不可撤消一旦获得授权,没有任何人可以取消。比如,你基于该产品代码开发了衍生产品,你不用担心会在某一天被禁止使用该代码。
7.1.3.2BSD (Berkeley Software Distribution)
-
伯克利软件分发许可协议
-
新 BSD 协议在软件分发方面,除需要包含一份版权提示和免责声明之外,没有任何限制。
-
MIT (Massachusetts Institute of Technology)
-
MIT许可协议之名源自麻省理工学院,又称“X许可协议”或“X11许可协议”
-
你可以自由使用,复制,修改,可以用于自己的项目。
-
可以免费分发或用来盈利。
-
唯一的限制是必须包含许可声明。
GPL (GNU General Public License)
GNU通用公共许可协议
-
可自由复制**你可以将软件复制到你的电脑,你客户的电脑,或者任何地方。复制份数没有任何限制。
-
可自由分**发在你的网站提供他人下载,拷贝到U盘送人。
-
可以用来盈利**你可以在分发软件的时候收费,但你必须在收费前向你的客户提供该软件的 GNU GPL 许可协议,以便让他们知道,他们可以从别的渠道免费得到这份软件,以及你收费的理由。
-
可自由修改**如果你想添加或删除某个功能,没问题,如果你想在别的项目中使用部分代码,也没问题,唯一的要求是,使用了这段代码的项目也必须使用 GPL 协议。
-
需要注意的是,分发的时候,需要明确提供源代码和二进制文件,另外,用于某些程序的某些协议有一些问题和限制,使用 GPL 协议,你必须在源代码代码中包含相应信息,以及协议本身。
-
LGPL (GNU Lesser General Public License)
-
GNU宽通用公共许可协议
-
LGPL 适合那些用于非 GPL 或非开源产品的开源类库或框架
-
MPL (Mozilla Public License)
-
Mozilla公共许可协议
注:在开发人员确定好要使用的第三方库时,应当发送相关使用范围说明文档。文档格式建议如下:
Developer
Project
ThirdParty
License
License Address
Ues
xxx
iPEMS
AFNetworking
MIT
https://github.com/AFNetworking/AFNetworking/
7.2资源文件的使用合法性
严禁使用从已上架App中拷贝使用任何资源文件,包括图片,音视频文件等。
网友评论