导语:
为规范部门 iOS开发,特制定本文档,供各位同仁参考。
语言
使用US英语, 不要使用拼音。e.g:
UIColor *myColor = [UIColor whiteColor];
禁止:
UIColor *yanse = [UIColor whiteColor];
命名规范
一、iOS命名两大原则是:
1. 可读性高(简洁且清晰)。遵循驼峰式样命名法。一般命名较长,比较语言话,能够使开发人员望文生义,一目了然名字的含义和使用方法。e.g:getLoadData
2.防止命名冲突。 每个模块都要加上自己的前缀,可以区分功能范畴并防止不同文件或类之间命名发生冲突。e.g:
相册模块 (PhotoGallery)
的代码都以 PG 作为前缀:PGDataManager
。
二、命名分类
命名分类主要有以下几类:
1.常量的命名。对于常量的命名最好在前面加上字母k作为标记. 如: static const NSTimeInterval kAnimationDuration = 0.3
。
Tips:
I. 若常量作用域超出编译单元(实现文件), 需要在类外可见时, 使用extern关键字, 并加上该类名作为前缀. 如extern NSString *const PGThumbnailSize
II.全局常量(通知或者关键字等)尽量用 const 来定义. 因为如果使用宏定义, 一来宏可能被重定义. 二来引用不同的文件可能会导致宏的不同. P.S. 对于
#define
也添加一下前缀k。
** 2.枚举的命名。**枚举类型命名要加相关类名前缀,并且枚举值命名要加枚举类型前缀。e.g:
typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {
UIViewAnimationTransitionNone,
UIViewAnimationTransitionFlipFromLeft,
UIViewAnimationTransitionFlipFromRight,
UIViewAnimationTransitionCurlUp,
UIViewAnimationTransitionCurlDown,
};
3. 变量和对象的命名。给一个对象命名时建议采用 “描述性的单词+变量类型 ” 的方式。e.g:
titleLabel //表示标题的label, 是UILabel类型 confirmButton //表示确认的button, 是UIButton类型
对于BOOL类型, 应加上is前缀, 比如- (BOOL)isEqualToString:(NSString *)aString
这样会更加清晰. 如果某方法返回非属性的 BOOL 值, 那么应根据其功能, 选用 has 或 is 当前缀, 如- (BOOL)hasPrefix:(NSString *)aString
4. 类的命名。类名(以及类别、协议名)应首字母大写,并以驼峰格式分割单词 。样式:前缀+描述+类型
注:前缀可以是你的姓名/昵称等主要用于团队开发的时候避免文件名重复
以下是我个人命名方式 e.g:
XJX --- 姓名/昵称
Message --- 描述性本类的功能
Cell/Model --- 类型/模型
1.类的前缀
1)所有类名、枚举、结构、protocol定义时最好加一个统一的标示符,可以是项目缩写,或者个人项目的名称缩写,例如都加上全大写的Hoo(我的姓氏)作为前缀
2)根据功能模块可以在给功能模块的类添加功能模块的名称前缀,如用户中心的 profileViewController
.可以命名为HooUCProfileViewController
.
2.类的后缀
所有protocol定义时,都加上后缀Delegate 。如,HooRefreshViewDelegate
,表示RefreshView的协议;
所有的控制器都加上Controller,所有的通知名都加上Notification。
5.方法命名
方法名应遵守小驼峰原则,首字母小写,其他单词首字母大写,每个空格分割的名称以动词开头。执行性的方法应该以动词开头,小写字母开头,返回性的方法应该以返回的内容开头,但之前不要加get。一些经典的操作应该使用约定的动词,如initWith,insert,remove,replace,add等等。e.g:
- (void)insertModel:(id)model atIndex:(NSUInteger)atIndex;
- (instancetype)arrayWithArray:(NSArray *)array;
6.@Property命名
尽量少用缩写, 使用Apple推荐的短句命名法命名且第一个字母必须小写, 这样子做, 可以省略再注释处理.
比如:
@property (readonly, copy) NSString *decomposedStringWithCanonicalMapping;
禁止:
@property (readonly, copy) NSString *uName;
7.图片命名
1)采用单词全拼,或者大家公认无岐义的缩写(比如:nav,bg,btn等)
2)采用“图标前缀+模块+功能”命名法,模块分为公共模块、私有模块。公共模块主要包括统一的背景。
样式:icon_单词缩写或全拼_功能。eg:
导航条背影图片:icon_nav_bar.png
导航返回按钮:icon_nav_back_normal.png
8.宏命名
宏命名要全部是大写字母,样式“模块+功能描述”,中间用下划线隔开。e.g:
#define BANNER_HEIGHT 215 //广告栏高度
9.通知类名称命名
通知方法中的类名命名方法,采用样式“NNKEY + 功能描述”,所有字母大写。所有通知类名称归类到一个单独的类文档中,如NotificationConstant.m,方便查找。e.g:
NSString *const NNKEY_MANAGEPWD = @"MANAGEPWD"; //验证密码
10.** UserDefault 的key值命名**
UserDefault 的key值命名必须全部大些,以NUDKEY开头,样式为:“NUDKEY+描述性单词”。 e.g:NSString *const NUDKEY_TOKEN = @"TOKEN";//本地密钥
所有的UerDefault的key值集中存放到一个常量文件中,e.g: ArchiveConstant.m文件中。
代码规范
一、iOS代码具备三个原则:
1.可复用。简单来说就是不要写重复的代码, 有重复的部分要尽量封装起来重用.
2.易维护。就是不要把代码复杂化, 不要去写巨复杂逻辑的代码, 而是把复杂的逻辑代码拆分开一个个小的模块, 这也是Do one thing的概念, 每个模块(或者函数)职责要单一,
3.可扩展。要求写代码时要考虑后面的扩展需求。
二、具体注意事项
1、 .h 文件:
- 不允许import 不必要的头文件,需要引入类用@class 替代。
- 不允许定义不必要的属性或变量。
- 不允许暴露过多的接口,原则上一般暴露一个依赖注入的接口供外部初始化。
- 不允许存在多个@interface 声明,有且只有一个@interface。
**2、.m文件 **
- 代码分区 #pragma mark - 代码分区说明 比如 "#pragma mark - 属性变量 #pragma mark - 界面布局"
- 代码分区顺序 从上到下依次是 析构,初始化,布局,私有方法,公有方法,网络,回调,属性变量。
- 所有的.m文件不得超过800行,超过请做适当逻辑分层。
- 任意行代码不能超过80字符。(其实也很容易超过80字符,可以考虑多行显示,比如有多个参数时,可以每个参数放一行。)可以在Xcode中设置超过80个字符的提醒,选中“preference->TextEditing->page guide at column ”.设置完之后就会在代码80个字符处有一条竖线。
- 代码折叠,这个可能是关于开发效率的,很有用。Xcode7默认没有开启代码折叠,如果你的方法体行数很长,看起来会很不方便,此时你就可以把方法“收起来”,一个类中的结构就会很清晰。开启方法如下:
“Xcode菜单-->Preferences-->Text Editing-->勾选Code folding ribbon”. - 所有类必须实现dealloc函数。
**3、关于函数 **
- 方法的声明与定义
- 函数名要能清晰的说明函数的功能。
- 参数名要能清晰的说明参数的含义。
- 按照当前函数依赖关系,选择是否内部函数做参数检查。
- 函数功能必须符合函数命名。
- 函数参数不得超过6个,超过请考虑传参数包。
- 所有函数不得超过150行。
**4、关于属性,变量 **
- 在类内部声明实例变量在名字前以下划线“_”开头。比如_xxx。
- 属性变量采用get方法做获取,俗称 “懒加载”。比如 存在属性变量 button.
@property (strong,nonatomic) UIButton *button;
#pragma mark - 属性变量
-(UIButton *)button
{
if (nil == _button)
{
_button = [UIButton buttonWithType:UIButtonTypeCustom];
}
return _button;
}
**5、语句规定 **
- 合理缩进,排版清晰。
- if 语句不得嵌套超过4层,超过请考虑设计是否合理。
- 风险代码请加 warning注释。
**6、关于通知,基类,策略,宏 **
- 任何新增一个通知,基类或者策略,宏都必须在团队内部讨论其必要性。
**7、工具类的整理 **
- 不允许将各种方法笼统的用一个 common类来做,
要做区分比如:涉及到字符串的可以放进 StringTools,颜色类的可以整理到 ColorTools 等。
**8、设计类遵照的原则 **
- 职责单一。
- 对外接口尽量追求简单好用,接口之间隔离。
**9、关于xib,storyboard **
- 原则上不允许使用xib,storyboard,尽量使用纯代码。
**10、防御性的工作 **
对nil的检查
• 仅在有业务逻辑需求时检查nil,而非为了防止崩溃。
• 向nil发送消息不会导致系统崩溃,Objective-C运行时负责处理。-
BOOL陷阱
Ojbective-C中定义BOOL为无符号字符型,这意味着BOOL类型可以有不同于YES(1)或者NO(0)的值。不要直接把整形转换成BOOL。常见的错误包括将数组的大小、指针值及位运算的结果直接转 换成BOOL,这取决于整型结果的最后一个字节,可能产生一个NO的值。当转换整形至BOOL时,使用三目操作符来返回YES或者NO。
• 将int值转换为BOOL时应特别小心。避免直接和YES比较。
• Objective-C中,BOOL被定义为unsigned char,这意味着除了 YES(1) 和NO(0)外它还可以是其他值。禁止将int直接转换(cast or convert)为BOOL。
• 常见的错误包括:将数组的大小、指针值或位运算符的结果转换(cast or convert)为BOOL,因为该BOOL值的结果取决于整型值的最后一位。
• 将整型值转换为BOOL的方法:使用三元运算符返回YES/NO,或使用位运算符(&&,||,!)BOOL、_Bool和bool之间的转换是安全的,但是BOOL和Boolean间的转换不是安全的,所以将Boolean看成整型值。
• 禁止直接将BOOL和YES/NO比较。建议:
BOOL great = [foo isGreat];
if (great)
//TODO…
禁止:
BOOL great = [foo isGreat];
if (great == YES)
//TODO...
- 属性
• 严把权限:对不需要外部修改的属性使用readonly。
• CFType使用@dynamic,禁止使用@synthesize。
• 属性声明为nonatomic,除非你需要原子操作。
**11、关于 UserDefault **
- 整个项目里面UserDefault 最多存储5个字段,并且相关存储字段应当说明注释在 UserDefault.h 文件里。
- 任何添加UserDefault字段行为必须经过讨论决定可行性
12、注释
- 注释原则:说明类职责、说明函数功能、说明变量、解释复杂或者特殊逻辑。
- 所有属性 和 方法都必须添加注释。
属性注释参考格式:
/*! @brief 传递照片的block **/
@property (nonatomic,copy) cameraImage photo;
方法注释参考格式:(同类别的多个方法要用 #pragma mark -
归类注释,方便查找)
#pragma mark - GET请求
/**
* @author 吴润源, 16-01-14 18:01:15
*
* GET请求一般常用方法
*
* @param urlStr 网络请求的url
* @param parameters 请求体参数
* @param success 请求成功回调
* @param failure 请求失败回调
*/
+(void)Get:(NSString *)urlStr parameters:(NSDictionary *)parameters success:(successBlock)success{
//默认网络提示框为NetworkRequestGraceTimeTypeNormal
[self Get:urlStr parameters:parameters graceTimeType:NetworkRequestGraceTimeTypeNormal success:success failure:^(NSError *error, NSURLSessionDataTask *operation) {
}];
}
三、代码优良习惯
1.判断nil或者YES/NO.
if (someObject) { ... } if (!someObject) { ... }
禁止:
if (someObject == YES) { ...} if (someObject != nil) { ...}
2.条件赋值
result = object ? : [self createObject];
禁止:
result = object ? object : [self createObject];
3.** BOOL赋值**
BOOL isAdult = age > 18;
禁止
BOOL isAdult;
if (age > 18)
{
isAdult = YES;
}
else
{
isAdult = NO;
}
4.** 拒绝死值**
if (car == Car.Nissan)
or
const int adultAge = 18;
if (age > adultAge) { ... }
禁止:
if (carName == "Nissan")
or
if (age > 18) { ... }
5.** 复杂的条件判断**
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;
}
禁止:
if (job.JobState == JobState.New
|| job.JobState == JobState.Submitted
|| job.JobState == JobState.Expired
|| (job.JobTitle && job.JobTitle.length))
{
//....
}
6.** 嵌套判断** (一旦发现某个条件不符合, 立即返回, 条理更清晰)
if (!user.UserName) return NO;
if (!user.Password) return NO;
if (!user.Email) return NO;
return YES;
禁止:
BOOL isValid = NO;
if (user.UserName)
{
if (user.Password)
{
if (user.Email) isValid = YES;
}
}
return isValid;
6.** 参数过多,传对象**
当发现实现某一功能需要传递的参数太多时, 就预示着你应该聚合成一个model类了
- (void)registerUser(User *user)
{
// to do...
}
禁止:
- (void)registerUserName:(NSString *)userName
password:(NSString *)password
email:(NSString *)email
{
// to do...
}
7.** 回调方法**
函数调用的可知性, 回调时被调用者要知道其调用者, 方便信息的传递, 所以建议在回调方法中第一个参数中加上调用者
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
8.** Block 的循环引用**
block中的self 要定义一个weakSelf 代替,用来避免循环引用问题。为了避免weak类型被释放,应在block内define 一个strong的self,进行非空判断。如果为空则抛出异常。如下:
__weak typeof(self) weakSelf = self;
myObj.myBlock = ^{
__strong typeof(self) strongSelf = weakSelf;
if (strongSelf) {
[strongSelf doSomething]; // strongSelf != nil
// preemption, strongSelf still not nil
[strongSelf doSomethingElse]; // strongSelf != nil
}
else {
// Probably nothing...
return;
}
};
9.** Block规范**
在函数中使用到Block时, Block第一行与代码块必须得空行, 无论方法是否是系统自带的.
比如:
- (void)someMethod {
[className blockName:^(parameter) {
// Code Body
}];
}
禁止:
- (void)someMethod {
[className blockName:^(parameter) {
// Code Body
}];
}
10.** 其他习惯**
- NSArray, NSDictionary成员的判空保护。在addObject或insertObject到NSArray或者NSDictionary时最好加一下判空保护,
- Observer的移除。凡是注册的Observer,切记至少在dealloc里面移除一下observer.
- commit代码之前一定要保证木有warning, 木有内存泄露, 确保都OK之后再上传代码. 其实很简单, 上传代码之前 Command + Shift + B 静态分析一下, 看看有木有什么issue.
四、代码样式规范
1.** 代码编写时候,添加空格或换行需注意:**
二元运算符和参数之间要有一个空格,如赋值号=左右各留一个空格。
self.myString = @"235423rew523452345";
一元运算符和参数之间不放置空格,比如!非运算符,&按位与,|按位或。
BOOL isClose = !isOpen;
强制类型转换和参数之间不放置空格。
NSString *str3 = (NSString*)self.myString;
三目运算符和参数之间必须得有空格, 如判断符"?"结果符":"。比如:
NSInteger userAge = @"Man" ? 18 : 19;
禁止:
NSInteger userAge = @"Man"?18:19;
- 长的变量值应该拆分为多行。尤其体现在使用数组或者字典。以下也分别是快速声明数组@[]和字典@{}的方法。元素应对齐。样式参考:
NSDictionary *dict = @{@"name":@"jack",
@"age":@"20",
@"gender":@"female",
@"isMarried":@"false"
};
2.**代码推荐**
* 项目采用MVC框架,页面要按模块分类存放到对应的文件夹。
* 对传入参数的保护或者说是否为空的判断,尽量不要使用if(!obj),而使用NSAssert断言来处理。NSAssert是系统定义的宏。
`NSAssert(myName != nil, @"myName参数为空");`
如果条件判断为真,则程序继续执行;
如果判断条件为假,则抛出异常,异常内容为后面定义的字符串;
* 方法参数名前一般使用"an","the","new"来进行修饰。代表类方法和实例方法的"+"加号,"-"减号后需要一个空格。如:
`- (void)setPersonInfo:(NSString*)theID theName:(NSString*)theName theAge:(NSInteger*)theAge`
* 当需要一定条件才执行某项操作时,最下边的应该是最重要的代码,不要将最重要的代码内嵌到if中。
如良好的风格是:
- (void) someMethod {
if(![someOther boolValue]) {
return;
}
//最重要的代码写在这里;
}
禁止:
- (void) someMethod {
if([someOther boolValue]) {
//重要代码;
}
}
* UIView的子类初始化的时候,不要进行任何的布局操作。布局操作应该在layoutSubviews里面做;需要重新布局的时候调用setNeedsLayout,而不要直接调用layoutSubviews
* 界面布局建议使用第三方控件masonry。代码简洁,逻辑清晰。
3.**代码对齐规范 **
* 任意行代码不能超过80个字符, 如果超过80个字符, 可以考虑多行显示, 比如有多个参数时, 可以每个参数放一行.
比如:
- (void)replaceObjectsInRange:(NSRange)range
withObjectsFromArray:(NSArray *)otherArray
range:(NSRange)otherRange;
* 在每个方法定义前需要留白一行, 也就是每个方法之间留空一行, 如果是系统的方法则可以不留空.
比如:
-
(void)viewDidLoad {
[super viewDidLoad];// Code Body
}
禁止:
-
(void)someMethod {}
[self someMethod];
}
* 一个实例变量出现属性赋值时, 如果是以“=”赋值, 必须以“=”对齐, 如果以“[]”赋值, 必须以开头第一个“[”对齐.
比如:
-
(void)setTextFieldDelegate:(id)textFieldDelegate {
self.phoneTextFieldOne.delegate = textFieldDelegate;
self.phoneTextFieldTwo.delegate = textFieldDelegate;
self.phoneTextFieldThree.delegate = textFieldDelegate;
self.reviewPassword.delegate = textFieldDelegate;
self.confirmPassword.delegate = textFieldDelegate;
}