iOS代码开发详细规范
一、命名
1.属性对象和局部变量(采用小驼峰命名法)
UI视图对象
变量名称 + (Label / Button / Cell / TableView / WebView / ScrollView / CollectionView / ImageView / TabBar / TextField ...)
如:UILabel *titleLabel;
特殊类型:UIBarButtonItem -> ButtonItem.
非UI视图对象
VC: name + (VC / TableVC / CollectionVC / PageVC)
如: UIPageViewController *firstPageVC;
特殊类型:
- UINavigationController -> NavCtroller
- UITapGestureRecognizer -> TapGesture
- UILongPressGestureRecognizer -> LongPressGesture
数据类型: name + 类型(Integer / Int / Long / Float / Double / Number / String / Array / MutableArray / IndexPath / Date / Error...)
如:NSArray *dataArray;
特殊类型:
- NSDictionnary -> Dict
- NSMutableDictionnary等可变类型变量,使用不可变类型的命名方式如:
name + Dict
- NSTimeInterval -> Double
- CGRect -> Rect, CGSize -> Size
注意:1.局部变量可不遵守变量名字后面加数据类型这一规则。 2.Bool类型以is、has、can等作为前缀,或以ing、ed等作后缀。
其他:
- name + Model
- name + ViewModel
- name + Request(网络请求)
- name + Block
注意:尽量为每个变量取有意义的名字,即“name”,但若确实没有name的情况下,直接采用如下形式,如:
UITableView *tableView;
2.实例变量
实例变量采用小驼峰命名法的基础上,以下划线“_
”作为前缀,如:
UIButton *_loginButton;
3.常量 和 宏常量
普通常量:以小写字母“k
”开头的驼峰命名法,如:static NSString *const kMovieCellHeight;
通知常量:一般形式为[ 触发通知的类名] + [Did 或 Will] + [ 动作 ] + Notification ;
宏常量:全部大写,中间用下划线“_
”作间隔,如:#define TARGET_OS_IOS
。
注意:
- 如果宏常量和APP业务相关则添加相应业务类名作前缀,如果适用于整个APP则直接使用APP前缀,如:
#define MOHomeCellHeaderHeight (18.0)
- 尽量不用宏来定义常量,而是采用枚举和const定义的常量
4.枚举类型名称 和 枚举变量名称
定义枚举类型:采用OC风格定义枚举类型,举个栗子:
typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {
UIViewAnimationTransitionNone, //默认从0开始
UIViewAnimationTransitionFlipFromLeft,
UIViewAnimationTransitionFlipFromRight,
UIViewAnimationTransitionCurlUp,
UIViewAnimationTransitionCurlDown,
};
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
UIViewAutoresizingFlexibleRightMargin = 1 << 2,
UIViewAutoresizingFlexibleTopMargin = 1 << 3,
UIViewAutoresizingFlexibleHeight = 1 << 4,
UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};
其中:
1.枚举类型名称采用首字母大写的驼峰命名法,并且添加相关类作为前缀
(本例中相关的类为UIView)
2.枚举值命名要添加枚举类型名作前缀
(本例中枚举值前缀分别为UIViewAnimationTransition和UIViewAutoresizing)
枚举变量名称和类型名称基本保持一致的情况下,尽量增强其可阅读性,采用小驼峰命名法,如:
UIViewAnimationTransition animationTransitionType;
5.方法名
采用小驼峰命名法,做到见其名知其含义,本身具有充分的解释性,拒绝无意义命名。
例外情况:
- 可以用一些通用的大写字母缩写打头的方法,比如 PDF,TIFF
- 可以用带下划线的前缀来命名私有方法或者类别中的方法
6.类名 和 协议名
类名:“统一的类名前缀” + “功能模块简称”(可省略) + “描述该类的词或词组” + 控件类型名尾缀(如Cell,ViewController,View等,可省略)
协议名:类名 + “Delegate”
都采用大驼峰命名法。
7.分类的名称 和 分类中方法名
分类的名称和类名的命名方式基本一致,只是前缀由开发者指定。
但分类中的方法名,需使用分类的前缀小写形式 + “_”的形式,如:
@interface NSDate (MOLocalDate)
/// 获取根据时间间隔按照系统时间计算的日期,seconds:单位(秒)
+ (NSDate *)mo_dateWithTimeIntervalSince1970:(NSTimeInterval)seconds;
8.Assets.xcassets文件命名
1.功能模块使用的资源,使用功能模块代码资源同样的文件夹结构(一级或多级)和名称。
2.公共或通用资源文件夹取名为“Common”或“Normal”
3.图片资源命名方式采用全部小写和分割线分隔的形式,前缀为功能模块名称或缩写,如:
common_navigation_bar_back
9.功能模块内文件夹命名
一般情况下,各功能模块下的文件夹命名包括但不限于以下命名:
- View 或 CustomView
- Model
- ViewController
- Cell
- ViewModel (MVVM模式下使用)
- Helper
- ……
10.Xib或Storyboard中命名
- Xib文件名和对应类名保持一致
- Xib或者Storyboard中的控件,命名采用全部大写且用一个空格分隔的形式。添加控件时立即添加有意义的命名,方便查看不同控件的约束关系以及后期维护。
总结:命名尽量不要用单词简写,注重保持语义明确;名称保持一致性,避免同样语义出现多种单词如“number”和“count”;命名风格统一,遵守统一的的命名规则。
二、风格
1.关于空格与空行
- 数组或字典字面量用逗号隔开的形式书写时,逗号后留有一个空格
-
if
或switch
语句中,这两个关键字和紧跟的括号之间留有一个空格 - 二元符号如"
=
"、"==
"、">
"、"<
"、"||
"、"&&
"等两侧留有一个空格 - 三元符号中"
?
"和":
"两侧留有一个空格 - 方法名“+”和“-”号和方法名中间留有一个空格;方法实现中“{”和方法名之间留有一个空格。
- 定义属性时,逗号后留有一个空格,如:
@property (nonatomic, copy, readonly) NSString *titleString;
小括号左右两边各用一个空格与外部隔开;
星号“*”左边留有一个空格。
- 声明类时
@interface MOCalendarService : NSObject
冒号左右各用一个空格隔开
- 声明分类或延展(类目)时
/// 分类
@interface NSDate (MOLocalDate)
/// 延展(类目)
@interface ClassName () <UICollectionViewDelegate,
UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
- 小括号左右两边各用一个空格与外部隔开
- 协议列表中,逗号后留有一个空格
- 方法与方法之间空一行,方法内的代码块与代码块之间都空一行
-
#prgma mark - Demo
分隔语句上方留有一空行,下方不留空行或留有一空行。
2.注释
- 在需要注释的方法或属性上方使用
Command + Alt + /
”快捷键,或者“/// + 一个空格 + 说明
”的形式进行注释,方便别处使用“Alt + 鼠标点按
”直接查询该方法或属性的定义。
(注意:在属性右侧使用“/// + 一个空格 + 说明
”的形式进行注释,在别处无法用“Alt + 鼠标点按
”快捷键获取注释详情) - 针对方法内某一行代码语句进行注释,可以在此行代码上方或右侧进行“
// + 一个空格 + 说明
”的形式进行注释,注意将该代码段和其它代码段用注释行或空白行进行分隔。 - 针对方法内某几行代码段进行注释,也注意将该代码段和其它代码段用注释行或空白行进行分隔。
总结:
- 注释方法和属性尽量方便代码使用时用快捷键获取注释内容
- 注释内容尽量让不懂代码的人能看懂在做的事情
- 注释代码行或代码段要明确区分注释区域
- 典型的需要注释的地方包括:头文件中类名、属性和方法注释,
if
或switch
语句注释,魔术数字或字符串注释 - 注释不要和Mark混淆使用。
3.Mark分区
建议开发时使用Xcode右下角的代码快捷(Code Snippet Library)方式保存和提取常用文件Mark分区结构。
针对VC:
#pragma mark - View Lifecycle
- (void)viewDidLoad {
[super viewDidLoad];
[self buildingUI];
[self makeViewConstraints];
[self bindViewModel];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
}
#pragma mark - buildingUI
- (void)buildingUI {
}
#pragma mark - Make Constraints
- (void)makeViewConstraints {
}
#pragma mark - bindViewModel
- (void)bindViewModel {
}
#pragma mark - Delegate Methods
#pragma mark - Publich Methods
#pragma mark - Private Methods
#pragma mark - Notification Event
#pragma mark - Property Set
#pragma mark - Property Get
针对ViewModel:
#pragma mark - Life Cycle
- (instancetype)init {
self = [super init];
if (self) {
[self binding];
}
return self;
}
#pragma mark - Bind
- (void)binding {
}
#pragma mark - Publich Methods
#pragma mark - Private Methods
#pragma mark - Property Set
#pragma mark - Property Get
针对TableViewController:
#import "<#ControllerName#>.h"
// static NSString *const <#cellNameId#> = @"<#cellId#>";
@interface <#ControllerName#> () <UITableViewDelegate, UITableViewDataSource>
// @property (nonatomic, strong) <#ViewModolClass#> *viewModel;
@property (nonatomic, strong) UITableView *tableView;
@end
@implementation <#ControllerName#>
#pragma mark - View Lifecycle
- (void)viewDidLoad {
[super viewDidLoad];
[self buildingUI];
[self makeViewConstraints];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
}
#pragma mark - buildingUI
- (void)buildingUI {
self.title = @"<#TitleName#>";
}
#pragma mark - Make Constraints
- (void)makeViewConstraints {
[self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view);
}];
}
#pragma mark - UITableViewDataSource && UITableViewDelegate
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return <#countInteger#>;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 44.f;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return 0.1f;
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
return 0.1f;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:<#cellNameId#>];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:<#cellNameId#>];
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:NO];
// do something
}
#pragma mark - Publich Methods
#pragma mark - Private Methods
#pragma mark - Notification Event
#pragma mark - Property Set
#pragma mark - Property Get
// - (<#ViewModolClass#> *)viewModel {
// if (!_viewModel) {
// _viewModel = [[<#ViewModolClass#> alloc] init];
// }
// return _viewModel;
//}
- (UITableView *)tableView {
if (!_tableView) {
_tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
_tableView.delegate = self;
_tableView.dataSource = self;
// _tableView.tableFooterView = [[UIView alloc] init];
// _tableView.showsVerticalScrollIndicator = NO;
// _tableView.contentInset = UIEdgeInsetsMake(0, 0, 50, 0);
[self.view addSubview:_tableView];
}
return _tableView;
}
@end
针对Cell:
#pragma mark - Init Method
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
[self makeViewConstraints];
}
return self;
}
#pragma mark - 约束布局
- (void) makeViewConstraints {
}
#pragma mark - Public Methods
#pragma mark - Private Methods
#pragma mark - Property Set
#pragma mark - Property Get
针对自定义View:
#pragma mark - Init Method
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self makeViewConstraints];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self makeViewConstraints];
}
return self;
}
#pragma mark - 约束布局
- (void) makeViewConstraints {
}
#pragma mark - Publich Methods
#pragma mark - Private Methods
#pragma mark - Property Set
#pragma mark - Property Get
4.对齐
- 所有代码段使用四个空格进行缩进
- 名字太长的方法定义或调用,可采用冒号对齐的方式多行展示
- 数组和字典字面量跨行书写时“@”符号对齐:
/// 我是数组
NSArray *demoArray = @[@"Object-C",
@"Swift",
@"Python",
@(YES),
@(1)
];
/// 我是字典
NSDictionary *demoDict = @{@"Object-C":@"Object-C",
@"Swift":@"Swift",
@"Python":@"Python",
@"BoolValue":@(YES),
@"Number":@(1)
};
其实,上面两个例子中,中括号和大括号的始末位置也进行了对齐。
-
if
语句中,左大括号“{”不跨行,并和右括号“)”之间留有一个空格; 右大括号“}”和对应的“if”保持左端对齐; else语句和左侧“}”以及右侧“{”在同一行,举个例子:
/// 我是 if 语句
if (18 == self.age) {
if (self.isGirl) {
NSLog(@"Marry me!");
} else {
/// do something
}
}
建议使用if语句时,直接选择苹果提供的快捷代码段。
- 所有枚举值与最左侧保持四个空格的缩进
- 非线程安全的属性以"
@property (nonatomic, ...)
"打头的形式书写,代码更工整 - block中嵌套block时,注意每个"}"和对应的代码起点对齐
三、开发习惯
1.Switch语句每个case后添加“{}”的习惯
- (void)sampleForSwitch {
SampleEnum testEnum = SampleEnumTwo;
switch(testEnum) {
caseSampleEnumUndefined: {
// do something
break;
}
caseSampleEnumOne: {
// do something
break;
}
caseSampleEnumTwo: {
// do something
break;
}
default: {
NSLog(@"WARNING: there is an enum type not handled properly!");
break;
}
}
}
2.先建立实体文件夹,后在工程中引入
3.条件语句
简单条件判断推荐使用三目运算符“? :
”,如:
result = object ? : [self createObject];
注意这里“?”后写成空格,会直接返回object的情况,不建议用下面这种形式:
result = object ? object : [self createObject];
4.Bool赋值
简单的条件判断后赋布尔值的逻辑,可以省略if
语句,如:
BOOL isAdult = age > 18;
而不是:
BOOL isAdult;
if (age > 18) {
isAdult = YES;
}
else {
isAdult = NO;
}
5.拒绝魔术数字和字符串
举个例子:
/// 反例1 “Nissan”字符串突然横空出现
if (carName == "Nissan")
/// 反例2 “18”是个什么意思~
if (age > 18) { ... }
推荐下面的方式:
/// 用枚举类型代替字符串类型(由于是数字类型比较,编译速度比字符串类型更为高效)
if (car == Car.Nissan)
/// 提前为魔术数字定义常量,同时方便添加注释(即明确数字含义后再继续写代码)
const int adultAge = 18;
if (age > adultAge) { ... }
6.readonly属性
不需要修改属性的地方,心怀添加readonly的念想。
7.copy属性
对于有可变类型子类的数据类型,使用copy属性防止数据被更改,如NSString、NSArray、NSDictionary。
8.使用字面量方式初始化数据,增强可读性
9.复杂判断简单化
if (job.JobState == JobState.New
|| job.JobState == JobState.Submitted
|| job.JobState == JobState.Expired
|| (job.JobTitle && job.JobTitle.length) {
....
}
可以被优化为:
/// 首先将上面整体提炼为一个方法
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;
}
10.嵌套判断平行化
BOOL isValid = NO;
if (user.UserName) {
if (user.Password) {
if (user.Email) {
isValid = YES;
}
}
}
return isValid;
可以被优化为:
if (!user.UserName) return NO;
if (!user.Password) return NO;
if (!user.Email) return NO;
return YES;
11.回调方法加调用者的习惯
如经典的:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
添加调用者tableView作为参数,方便信息传递和区分调用者。
12.NSTimer、观察者、通知、网络请求等注意在Dealloc或viewWillDisappear:方法中移除。同一个文件中,移除的顺序应和创建的顺序保持一致,方便后期维护(注意:工程中使用RAC或已经封装好的方法不需要作移除操作)。
13.访问或操作NSArray、NSDictionary等对象时,注意判断对象和对象内要访问的元素是否为nil。
14.虽然很简单,但尽量不用new方法而是统一采用Cocoa规范中的[[ClassName alloc] init]方法
15.Delegate使用weak属性修饰
@property (nonatomic, weak) delegate;
16.提交代码前保证无warning和error
17.随时注释别人或未来的自己有可能看不懂的代码
18.逻辑捋清楚前不要写代码,因为一定会重写。
四、设计思想
1.精简
- 方法不超过约一百行,否则就要考虑拆分
- 清除:无用类、方法、资源、注释、多余空行、Log语句、警告
2.分工明确
- .h文件:核心属性和方法声明地带 (
.h
文件由于不参与实现过程,用@class
引用类,将#import "Demo.h"
形式的引入统一写在.m
文件中) - .m文件:私有方法、私有变量声明以及所有方法的实现基地
- 分类:不常用、或与主业务无关的方法聚居地(不要滥用分类)
- View层:UI搭建(MVC设计模式下可以进行简单的数据展示)
- Model层:展示最直白的数据结构
- ViewController层:
- 1.容器:容纳子VC,构建View布局和展示
- 2.控制Viewmodel
- 3.响应UI事件(或信号)和代理方法
- 4.不同生命周期逻辑处理
- ViewModel层:用来且只用来处理和数据相关的一切
- 1.数据初始化
- 2.请求网络数据
- 3.数据业务处理:数据持久化、筛选、排序、验证、增删改查
- 4.将处理后的数据在View上展示
- 5.头文件中返回最终有效readonly数据
- 网络层:分担ViewModel层网络请求职责