iOS代码规范
前言
由于iOS端项目刚刚创立,又恰逢项目快速迭代,为了快速完成业务功能需求,造成项目人员各自开发,造成了项目代码的不统一。为了增加代码的可读性和可维护性,需要对代码进行统一的规范,以减少项目人员熟悉代码的时间和障碍。此文可作为iOS团队Objective-C语言编程规范,如有更好建议,欢迎提出。
一、核心原则
1、代码应该简洁易懂,逻辑清晰
- 代码简单明了,不过分追求技巧,降低程序的可读性
- 简洁的代码也会降低出现bug的可能性,同时在出现bug的时候可以更加快速的进行定位
- 代码的清晰性优先于性能
2、优秀的组织性
- 项目目录结构要清晰明了
- 类中的代码结构要清晰明了
3、良好的命名规范
- 文件名、方法名、属性名、资源名,都要统一的命名规范
4、良好的注释文档
- 尽可能多的给代码做注释
- 待项目稳定后,因需求和bug修改的地方,需注释说明
- 每个类、主要的方法、主要的属性必须做注释
具体的目录结构、命名规则、注释规则后续详细说明。
二、项目结构
1、整体结构
image2、主要模块结构
按每个TabBar模块区分,另外拆分出来个人中心、登录注册两大模块,在其他中是Base、nav、tabBar等其他的模块
image
3、子模块结构
在每个子模块中,都采用MVC的文件夹分类,且加入Manager文件夹,进行相应的逻辑Manager的存放
image
三、代码风格
- import规范:
import按照Controller、View、Model、Manager工具、分类的顺序依次从上往下写,不同类别用空行隔开,具体代码规范如下:
#import "ClassroomTableVC.h"
#import "ClassPracticePhotoVC.h"
#import "ClassPracticeReportVC.h"
#import "PromotePaperVC.h"
#import "PaperPreviewVC.h"
#import "PromoteReportVC.h"
#import "ActivateVC.h"
#import "SpokenEnglishVC.h"
#import "SpokenChineseVC.h"
#import "FeedbackVC.h"
#import "EBookVC.h"
#import "TYAlertController.h"
#import "WrongTrainVC.h"
#import "TripleHeaderView.h"
#import "AVPlayerView.h"
#import "TripleLevelTableViewCell.h"
#import "ClassroomHomeModel.h"
#import "ClassroomShowModel.h"
#import "StudyTaskManager.h"
#import "AMapLocation.h"
#import <CCVodSDK/DWPlayInfo.h>
#import <CCVodSDK/DWPlayerView.h>
#import <CCVodSDK/DWVodVideoModel.h>
#import "UIDevice+TFDevice.h"
#import "UIButton+TouchAreaInsets.h"
#import "UIView+TYAlertView.h"
- 类扩展申明规范:
1.常量、静态变量声明在前
2.成员变量声明再{}中,注意大括号的格式
3.@property声明同一类别放在一起,不同类别换行写
4.包括空格规范
几个注意点如下代码示例:
static CGFloat cellHeight = 48;
static NSString *kTripleLeveleCellId = @"TripleLevelTableViewCell";
@interface ClassroomTableVC () <UITableViewDelegate, UITableViewDataSource, DWVideoPlayerDelegate>
{
NSInteger selectedIndex;//题目数量选择
}
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) UIView *headerView;
@property (nonatomic, strong) AVPlayerView *playerView;//播放器
@property (nonatomic, strong) TsaaImgView *sectionOneImageView;
@property (nonatomic, strong) ClassroomHomeModel *model;
@property (nonatomic, strong) NSMutableArray *categoryTitleArray;
@property (nonatomic, strong) NSMutableArray *dataArray;
@property (nonatomic, strong) NSString *clickId;
@property (nonatomic, assign) NSInteger openSection;
- 方法编写规范:
1.前括号提至方法后
2.同一模块功能代码写在一起
3.不同模块功能换行写
- (void)viewDidLoad {
[super viewDidLoad];
[self setNav];
[self initSth];
[self configureUI];
}
- 方法归类:
在#pragma mark下面,代码上空一行,下空两行展示,示例如下:
#pragma mark - Life Cycle
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
}
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)dealloc {
}
#pragma mark - Private
- (void)loadSet {
}
- (void)setNav {
}
- (void)configureUI {
}
#pragma mark - Action
- (void)navTitleAction:(UIButton *)sender {
}
#pragma mark - Request
- (void)loadData {
[self getSubjectListRequest:NO];
}
- (void)getSubjectListRequest:(BOOL)refresh {
}
四、命名规范
一、统一要求
- 含义清晰,尽量做到不需要注释就能了解其作用,否则尽可能添加注释说明
- 使用全称,不要使用缩写
二、类
-
前缀:
采用ZXK作为每个类的前缀,以防止与其他三方的类可能的命名冲突
例如:
ZXKSettingViewController -
大驼峰式命名:
每个单词的首字母都采用大写字母
例如:
ZXKSettingViewController -
后缀:
ViewController: 使用ViewController做后缀
例子: ZXKHomeViewController
View: 使用View做后缀
例子: ZXKAlertView
UITableCell:使用Cell做后缀
例子: ZXKSettingCell
Protocol: 使用Delegate或者DataSource作为后缀
例子: UITableViewDelegate
UI控件依次类推
三、变量
-
成员变量:
成员变量以_开头,第一个单词首字母小写
例子:
@interface Person ()
{
NSString *_personName;
}
@end -
局部变量:
推荐使用长的、描述性的方法和变量名。
推荐:
UIButton *settingsButton;
不推荐:
UIButton *setBut;
四、常量
常量应该以驼峰法命名,并以相关类名作为前缀。
推荐:
static const NSTimeInterval ZXKSignInViewControllerFadeOutAnimationDuration = 0.4;
不推荐:
static const NSTimeInterval fadeOutTime = 0.4;
推荐使用常量来代替字符串字面值和数字,这样能够方便复用,而且可以快速修改而不需要查找和替换。常量应该用 static 声明为静态常量,而不要用 #define,除非它明确的作为一个宏来使用。
推荐:
static NSString * const ZXKCacheControllerDidClearCacheNotification = @"ZXKCacheControllerDidClearCacheNotification";
static const CGFloat ZXKImageThumbnailHeight = 50.0f;
不推荐:
#define CompanyName @"Apple Inc."
#define magicNumber 42
常量应该在头文件中以这样的形式暴露给外部:
extern NSString *const ZOCCacheControllerDidClearCacheNotification;
并在实现文件中为它赋值。
只有公有的常量才需要添加命名空间作为前缀。尽管实现文件中私有常量的命名可以遵循另外一种模式,你仍旧可以遵循这个规则。
五、属性
1、属性的命名使用小驼峰
推荐这样写:
@property (nonatomic, readwrite, strong) UIButton *confirmButton;
2、 属性的关键字推荐按照 原子性,读写,内存管理的顺序排列
推荐这样写:
@property (nonatomic, readwrite, copy) NSString *name;
@property (nonatomic, readonly, copy) NSString *gender;
@property (nonatomic, readwrite, strong) UIView *headerView;
3、 Block属性应该使用copy关键字
推荐这样写:
typedef void (^ErrorCodeBlock) (id errorCode,NSString *message);
@property (nonatomic, readwrite, copy) ErrorCodeBlock errorBlock;//将block拷贝到堆中
4、形容词性的BOOL属性的getter应该加上is前缀
推荐这样写:
@property (assign, getter=isEditable) BOOL editable;
5、使用getter方法做懒加载
实例化一个对象是需要耗费资源的,如果这个对象里的某个属性的实例化要调用很多配置和计算,就需要懒加载它,在使用它的前一刻对它进行实例化:
- (NSDateFormatter *)dateFormatter {
if (!_dateFormatter) {
_dateFormatter = [[NSDateFormatter alloc] init];
NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
[_dateFormatter setLocale:enUSPOSIXLocale];
[_dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS"];
}
return _dateFormatter;
}
但是也有对这种做法的争议:getter方法可能会产生某些副作用,例如如果它修改了全局变量,可能会产生难以排查的错误。
五、宏
-
全部大写,单词中间用_隔开
#define REFRESH_HEADER_HEIGHT 52.0f //刷新view的高度 -
以k开头,采用大驼峰命名
#define kScreenWidth [[UIScreen mainScreen] bounds].size.width -
带参数,采用小驼峰命名
#define registerNib(xibName) [UINib nibWithNibName:xibName bundle:nil]
六、枚举
- Enum类型的命名与类的命名规则一致
- Enum中枚举内容的命名需要以该Enum类型名称开头
typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) {
AFNetworkReachabilityStatusUnknown = -1,
AFNetworkReachabilityStatusNotReachable = 0,
AFNetworkReachabilityStatusReachableViaWWAN = 1,
AFNetworkReachabilityStatusReachableViaWiFi = 2
};
五、格式规范
一、函数
1、函数尽可能简单明了
通常来说,在阅读一个函数的时候,如果视需要跨过很长的垂直距离会非常影响代码的阅读体验。如果需要来回滚动眼球或代码才能看全一个方法,就会很影响思维的连贯性,对阅读代码的速度造成比较大的影响。最好的情况是在不滚动眼球或代码的情况下一眼就能将该方法的全部代码映入眼帘。
2、一个函数只做一件事(单一原则)
例如:
[self initData];
[self configureUI];
3、对于有返回值得函数,每一个分支都必须有返回值
+ (NSString *)getSubjectNameWithSubjectId:(NSString *)subjectId {
if ([subjectId isEqualToString:@"01"]) {
return @"语文";
}
else if ([subjectId isEqualToString:@"02"]) {
return @"数学";
}
else if ([subjectId isEqualToString:@"03"]) {
return @"英语";
}
else if ([subjectId isEqualToString:@"06"]) {
return @"物理";
}
else if ([subjectId isEqualToString:@"07"]) {
return @"生物";
}
else if ([subjectId isEqualToString:@"08"]) {
return @"化学";
}
else if ([subjectId isEqualToString:@"04"]) {
return @"道德与法治";
}
else if ([subjectId isEqualToString:@"05"]) {
return @"历史";
}
else if ([subjectId isEqualToString:@"09"]) {
return @"地理";
}
else if ([subjectId isEqualToString:@"10"]) {
return @"科学";
}
else if ([subjectId isEqualToString:@"11"]) {
return @"历史与社会";
}
else {
return @"";
}
}
最后的else也要加上
4、如果在不同的函数内部有相同的功能,应该把相同的功能抽取出来单独作为另一个函数
5、将函数内部比较复杂的逻辑提取出来作为单独的函数
一个函数内的不清晰(逻辑判断比较多,行数较多)的那片代码,往往可以被提取出去,构成一个新的函数,然后在原来的地方调用它这样你就可以使用有意义的函数名来代替注释,增加程序的可读性。
6、函数名中不应使用and,而且签名要与对应的参数名保持高度一致
推荐这样写:
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;
不推荐这样写:
- (instancetype)initWithWidth:(CGFloat)width andHeight:(CGFloat)height;
- (instancetype)initWith:(int)width and:(int)height;
7、函数中{}的写法
紧跟在函数名后面,不要另起一行写
推荐这样写:
- (instancetype)init {
self = [super init];
if (self) {
}
return self;
}
不推荐这样写:
- (instancetype)init
{
self = [super init];
if (self) {
}
return self;
}
8、 方法实现时,如果参数过长,则令每个参数占用一行,以冒号对齐
例如:
- (void)doSomethingWith:(NSString *)theFoo
rect:(CGRect)theRect
interval:(CGFloat)theInterval {
//Implementation
}
二、if语句
1、每个分支else另起一行,而不是跟在 } 后面
推荐这样写:
if (aaa) {
return;
}
else {
return;
}
不推荐这样写:
if (aaa) {
return;
} else {
return;
}
2、必须列出所有分支(穷举所有的情况),而且每个分支都必须给出明确的结果。
推荐这样写:
+ (NSString *)isSaveClassroomSubjectId:(NSString *)subjectId {
if ([subjectId isEqualToString:GetClassRoomSubjectId]) {
return @"1";
}
else {
return @"";
}
}
不推荐这样写:
+ (NSString *)isSaveClassroomSubjectId:(NSString *)subjectId {
if ([subjectId isEqualToString:GetClassRoomSubjectId]) {
return @"1";
}
}
3、不要使用过多的分支,要善于使用return来提前返回错误的情况
推荐这样写:
- (void)someMethod {
if (!goodCondition) {
return;
}
//Do something
}
不推荐这样写:
- (void)someMethod {
if (goodCondition) {
//Do something
}
}
4、条件表达式如果很长,则需要将他们提取出来赋给一个BOOL值
推荐这样写:
BOOL isCurrentIndex = (self.currentIndex == 0);
BOOL shouldScroll = [self.name isEqualToString:@"hello"];
BOOL isSuccess = isCurrentIndex && shouldScroll;
if (isSuccess) {
//Do something
}
不推荐这样写:
if (self.currentIndex == 0 && [self.name isEqualToString:@"hello"]) {
//Do something
}
三、switch语句
推荐这样写:
switch (integer) {
case 1: {
// ...
}
break;
case 2: {
// ...
break;
}
case 3: {
// ...
break;
}
default: {
// ...
break;
}
四、注释
优秀的代码大部分是可以自描述的,我们完全可以用程代码本身来表达它到底在干什么,而不需要注释的辅助。
但并不是说一定不能写注释,有以下三种情况比较适合写注释:
公共接口(注释要告诉阅读代码的人,当前类能实现什么功能)。
涉及到比较深层专业知识的代码(注释要体现出实现原理和思想)。
容易产生歧义的代码(但是严格来说,容易让人产生歧义的代码是不允许存在的)。
除了上述这三种情况,如果别人只能依靠注释才能读懂你的代码的时候,就要反思代码出现了什么问题。
最后,对于注释的内容,相对于“做了什么”,更应该说明“为什么这么做”。
参考文档:
https://www.jianshu.com/p/2093cdaa2f96
https://www.jianshu.com/p/9b10331970a5
https://www.jianshu.com/p/08be5b30ff82
https://www.cnblogs.com/gfxxbk/p/5469017.html
https://www.jianshu.com/p/8165a220e125
https://github.com/oa414/objc-zen-book-cn