Ios开发之代码规范
June 13, 2016
iOS开发之代码规范
目的
统一规范XCode编辑环境下Objective-C 的代码风格和标准。
适用范围
适用于所有的Objective-C 开发的项目以及以后将会代替OC的Swift语言的项目。
命名规范
总的来说, iOS命名两大原则是:可读性高
和防止命名冲突
(通过加前缀来保证). Objective-C 的命名通常都比较长, 名称遵循小驼峰式命名法
.
一个好的命名标准很简单, 就是做到在开发者一看到名字时, 就能够懂得它的含义和使用方法.
另外, 每个模块都要加上自己的前缀, 前缀在编程接口中非常重要, 可以区分软件的功能范畴并防止不同文件或者类之间命名发生冲突, 比如相册模块(PhotoGallery)的代码都以PG作为前缀: PGAlbumViewController, PGDataManager.
常量的命名
对于常量的命名最好在前面加上字母k作为标记. 如:
static const NSTimeInterval kAnimationDuration = 0.3;
定义作为NSDictionary或者Notification等的Key值字符串时加上const关键字, 以防止被修改. 如:
NSString *const UIApplicationDidEnterBackgroundNotification
Tips:
若常量作用域超出编译单元(实现文件), 需要在类外可见时, 使用extern关键字, 并加上该类名作为前缀. 如 extern NSString *const PGThumbnailSize
全局常量(通知或者关键字等)尽量用const来定义. 因为如果使用宏定义, 一来宏可能被重定义. 二来引用不同的文件可能会导致宏的不同. P.S. 对于#define也添加一下前缀k(强迫症, 哈哈…)
枚举的命名
对于枚举类型, 经常会看到之前的C的定义方式:
typedef enum : {
CameraModeFront,
CameraModeLeft,
CameraModeRight,
} CameraMode;
每次看到这种定义方式总是感觉怪怪的, 作为一个正宗的iOS开发者当然要以Objective-C的方式来定义啦, 哈哈… 那Objective-C是怎么定义的呢? 很简单, 到SDK里面看看Apple是怎么做滴:
typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {
UIViewAnimationTransitionNone,
UIViewAnimationTransitionFlipFromLeft,
UIViewAnimationTransitionFlipFromRight,
UIViewAnimationTransitionCurlUp,
UIViewAnimationTransitionCurlDown,
};
这边需要注意的是: 枚举类型命名要加相关类名前缀并且枚举值命名要加枚举类型前缀.
变量和对象的命名
给一个对象命名时建议采用修饰+类型的方式. 如果只用修饰命名会引起歧义, 比如title (这个到底是个NSString还是UILabel?). 同样的, 如果只用类型来命名则会缺失作用信息, 比如label (好吧, 我知道你是个UILabel, 但是我不知道它是用来做什么的呀?).
正确的命名方式为:
titleLabel //表示标题的label, 是UILabel类型
confirmButton //表示确认的button, 是UIButton类型
对于BOOL类型, 应加上is前缀, 比如
- (BOOL)isEqualToString:(NSString *)aString
这样会更加清晰. 如果某方法返回非属性的 BOOL 值, 那么应根据其功能, 选用 has 或 is 当前缀, 如
- (BOOL)hasPrefix:(NSString *)aString
Tip: 如果某个命名已经很明确了, 为了简洁可以省去类型名. 比如scores, 很明显是个array了, 就不必命名成scoreArray了
小结:方法的名称应全部使用有意义的单词组成,且以小写字符开头,多单词组合时,后面的单词首字母大写。
方法中的参数;第一个参数名称要从函数名称上携带出来,第二个参数的首字母小写,多个单词组合时,后面单词的首字母大写。参数有别名时,参数别名与参数名一致。
例如:
-(void)myFunctionWithSizeA:(CGSize)sizeA sizeB:(CGSize)sizeB;
编码规范
编码规范简单来说就是为了保证写出来的代码具备三个原则:可复用
, 易维护
, 可扩展
. 这其实也是面向对象的基本原则.
可复用, 简单来说就是不要写重复的代码, 有重复的部分要尽量封装起来重用. 否则修改文件的时候得满地找相同逻辑的地方.
易维护, 就是不要把代码复杂化, 不要去写巨复杂逻辑的代码, 而是把复杂的逻辑代码拆分开一个个小的模块, 这也是Do one thing的概念, 每个模块(或者函数)职责要单一, 这样的代码会易于维护, 也不容易出错.
可扩展, 则是要求写代码时要考虑后面的扩展需求, 这个属于架构层面的东东, 利用对应的设计模式来保证.
判断nil或者YES/NO
Preferred:
if (someObject) { ... }
if (!someObject) { ... }
Not preferred:
if (someObject == YES) { ...}
if (someObject != nil) { ...}
if (someObject == YES)
容易误写成赋值语句, 自己给自己挖坑了…而且if (someObject)写法很简洁, 何乐而不为呢?
条件赋值
Preferred:
result = object ? : [self createObject];
Not preferred:
result = object ? object : [self createObject];
如果是存在就赋值本身, 那就可以这样简写, 多简洁啊, 哈哈…
初始化方法
Preferred:
NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve"];
NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingZIPCode = @10018;
第一个好处还是简洁, 第二个好处是可以防止初始化进去nil值造成crash
定义属性
Preferred:
@property (nonatomic, readwrite, copy) NSString *name;
建议定义属性的时候把所有的参数写全, 尤其是如果想定义成只读的(防止外面修改)那一定要加上readonly, 这也是代码安全性的一个习惯.
如果是内部使用的属性, 那么就定义成私有的属性(定义到.m的class extension里面)
对于拥有Mutable子类型的对象(e.g. NSString, NSArray, NSDictionary)一定要定义成copy属性. Why? 示例: NSArray的array = NSMutableArray的mArray; 如果mArray在某个地方改变了, 那array也会跟着改变. So, make sense?
尽量不要暴露mutable类型的对象在public interface, 建议在.h定义一个Inmutable类型的属性, 然后在.m的get函数里面返回一个内部定义的mutable变量. Why? For security as well!
BOOL赋值
Preferred:
BOOL isAdult = age > 18;
Not preferred:
BOOL isAdult;
if (age > 18)
{
isAdult = YES;
}
else
{
isAdult = NO;
}
为什么要这么写呢, 我不告诉你, 哈哈哈…
拒绝空值
Preferred:
if (car == Car.Nissan)
or
const int adultAge = 18; if (age > adultAge) { ... }
Not preferred:
if (carName == "Nissan")
or
if (age > 18) { ... }
死值每次修改的时候容易被遗忘, 地方多了找起来就悲剧了. 而且定义成枚举或者static可以让错误发生在编译阶段. 另外仅仅看到一个数字, 完全不知道这个数字代表的意义. 纳尼?
复杂的条件判断
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))
{
//....
}
清晰明了, 每个函数DO ONE THING!
嵌套判断
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;
一旦发现某个条件不符合, 立即返回, 条理更清晰
参数过多
Preferred:
- (void)registerUser(User *user)
{
// to do...
}
Not preferred:
- (void)registerUserName:(NSString *)userName
password:(NSString *)password
email:(NSString *)email
{
// to do...
}
当发现实现某一功能需要传递的参数太多时, 就预示着你应该聚合成一个model类了…这样代码更整洁, 也不容易因为参数太多导致出错.
回调方法
Preferred:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
函数调用的可知性, 回调时被调用者要知道其调用者, 方便信息的传递, 所以建议在回调方法中第一个参数中加上调用者
Block的循环引用
Block确实是个好东西, 但是用起来一定要注意循环引用的问题.
__weak typeof(self) weakSelf = self;
dispatch_block_t block = ^{
[weakSelf doSomething]; // weakSelf != nil
// preemption, weakSelf turned nil
[weakSelf doSomethingElse]; // weakSelf == nil
};
如此在上面定义一个weakSelf, 然后在block体里面使用该weakSelf就可以避免循环引用的问题. 那么问题来了…是不是这样就完全木有问题了? 很不幸, 答案是NO, 还是有问题. 问题是block体里面的self是weak的, 所以就有可能在某一个时段self已经被释放了, 这时block体里面再使用self那就是nil, 然后…然后就悲剧了…那么肿么办呢?
__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;
}
};
解决方法很简单, 就是在block体内define一个strong的self, 然后执行的时候判断下self是否还在, 如果在就继续执行下面的操作, 否则return或抛出异常.
什么情况下会出现block里面self循环引用的问题? 这个问题问的好, 哈哈…简单来说就是双边引用, 如果block是self类的property (此时self已经retain了block), 然后在block内又引用了self, 这个情况下就肯定会循环引用了…
注释规范
注释可以
/**/
和//
两种注释符号,涉及到多行注释的时候,尽量使用/**/
。对于一行代码的注释可放在前一行及本行上,不予许放在下一行,更不允许在一行语句中间加入注释。
不要每行都去加注释,显而易见的地方就不必加注释了。
比如:
- Model一定要为每一个参数增加注释.
修改规范
新增的代码行
新增的代码行前后应有注释行说明
//修改人,修改时间,修改说明
新增代码行
//修改结束删除的代码行
删除的代码行前后应有注释行说明//修改人,修改时间,修改说明
要删除的代码行(将要删除的语句进行注释)
//修改结束
目录结构规范
sooc-ios_new
sooc-ios_new
AppDelegate
Classes_New 界面代码目录登录
主页
我我的消息
OtherOther
Common_Super 基本界面\Super界面类
TabbarViewController
MMViewController
OtherLibThree 第三方代码目录
Model 模型目录
Category 自定义category目录
Util 工具目录网络请求工具
用户资料处理工具
(举例)极光数据处理工具
CommonFunction 通用功能工具类 (如拨打客服电话类方法)
OtherRes 存放应用基本数据文件
Images.xcassets 图片资源存放xcassets文件
SCAPI.h API数据存放文件
SCConfigDefine.h 配置信息存放文件(如:友盟KEY)
SCColorDefine.h 颜色信息存放文件 (如默认ViewController背景色)
SCTypeDefine.h 枚举的汇总集合 (只放通用枚举)
SCMacros.h 宏
.pch 通用.h汇总文件
OtherSupporting Files 系统默认配置文件
sooc-ios_newTests Tests代码目录
sooc-ios_newUITests UITests代码目录
Products 项目包目录
Pods cocoaPods配置文件目录
Frameworks 框架存放目录Pod cocoaPods资源部分
所有的三级文件目录及子目录创建过程中, 一定要先在物理路径中创建相应的文件夹, 再拖拽至项目中!
添加文件夹方式:
代码放置规范
#define放置规范
#define
因有安全性的问题, 所以绝对不允许添加至SCMacros以外的类中. 如:
在 *AddedCourseViewController* 中出现
#define V_W self.view.frame.size.width
当前viewController如需要此需求, 可添加私有属性或者基础类添加基础属性.
当前viewController,如需常数宏, 则写为常量的方式.