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;
  1. 建议定义属性的时候把所有的参数写全, 尤其是如果想定义成只读的(防止外面修改)那一定要加上readonly, 这也是代码安全性的一个习惯.

  2. 如果是内部使用的属性, 那么就定义成私有的属性(定义到.m的class extension里面)

  3. 对于拥有Mutable子类型的对象(e.g. NSString, NSArray, NSDictionary)一定要定义成copy属性. Why? 示例: NSArray的array = NSMutableArray的mArray; 如果mArray在某个地方改变了, 那array也会跟着改变. So, make sense?

  4. 尽量不要暴露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, 这个情况下就肯定会循环引用了…

注释规范

  1. 注释可以/**///两种注释符号,涉及到多行注释的时候,尽量使用/**/

  2. 对于一行代码的注释可放在前一行及本行上,不予许放在下一行,更不允许在一行语句中间加入注释。

  3. 不要每行都去加注释,显而易见的地方就不必加注释了。

    比如:

登录注释

  1. Model一定要为每一个参数增加注释.

修改规范

  1. 新增的代码行

    新增的代码行前后应有注释行说明

    //修改人,修改时间,修改说明
    新增代码行
    //修改结束
  2. 删除的代码行
    删除的代码行前后应有注释行说明

    //修改人,修改时间,修改说明
    要删除的代码行(将要删除的语句进行注释)
    //修改结束

目录结构规范

sooc-ios_new

sooc-ios_new

AppDelegate
Classes_New 界面代码目录

登录
主页

我的消息
Other

Other

Common_Super 基本界面\Super界面类

TabbarViewController
MMViewController
Other

LibThree 第三方代码目录
Model 模型目录
Category 自定义category目录
Util 工具目录

网络请求工具
用户资料处理工具
(举例)极光数据处理工具
CommonFunction 通用功能工具类 (如拨打客服电话类方法)
Other

Res 存放应用基本数据文件

Images.xcassets 图片资源存放xcassets文件
SCAPI.h API数据存放文件
SCConfigDefine.h 配置信息存放文件(如:友盟KEY)
SCColorDefine.h 颜色信息存放文件 (如默认ViewController背景色)
SCTypeDefine.h 枚举的汇总集合 (只放通用枚举)
SCMacros.h 宏
.pch 通用.h汇总文件
Other

Supporting Files 系统默认配置文件

sooc-ios_newTests  Tests代码目录
sooc-ios_newUITests UITests代码目录
Products 项目包目录
Pods cocoaPods配置文件目录
Frameworks 框架存放目录

Pod cocoaPods资源部分

参考DEMO

所有的三级文件目录及子目录创建过程中, 一定要先在物理路径中创建相应的文件夹, 再拖拽至项目中!

添加文件夹方式:

文件夹添加方式

代码放置规范

#define放置规范

#define因有安全性的问题, 所以绝对不允许添加至SCMacros以外的类中. 如:

在 *AddedCourseViewController* 中出现 
#define V_W self.view.frame.size.width

当前viewController如需要此需求, 可添加私有属性或者基础类添加基础属性.

当前viewController,如需常数宏, 则写为常量的方式.