@(〓〓 iOS-实用技术)[Objective-C编码规范]
- 作者: Liwx
- 邮箱: 1032282633@qq.com
目录
- 11.Objective-C 编码规范指南
- 项目工程结构
- 代码结构
- 项目Xode相关配置
- 代码缩进配置
- 代码行号显示配置
- 项目编码建议
- 注释
- 方法注释
- 属性注释
- 命名与编码规范
- 类名命名规则
- 协议编码规则
- 控件/控制器定义命名规则
- 方法
- 方法名和参数命名规则
- 方法声明和编码规范
- 方法调用父类方法编码规则
- 函数
- 变量
- 成员属性编码规则
- 常量
- Foundation框架常量赋值规则
- 定义普通常量以字母k开头
- 枚举与宏定义
- 枚举编码规则
- NS_ENUM定义普通枚举
- NS_OPTIONS定义位枚举
- 通知和异常
- 通知NSNotification常量命名规则
- 异常命名规则
- 布尔值
- 条件语句
- if else 条件语句
- switch case 语句
- 其他规则
- 初始化方法(构造方法)规则
- 直接放回对应数据时,无需添加get, calc单词
- 单例的声明和使用规则
- 设置常用属性,直接使用点语法
- 协议和代理方法命名规则
- 可以使用({})语法模块化设置控件属性(可选)
项目工程结构
代码结构
-
实现文件中的代码结构,提倡以下约定:
- 用
#pragma mark -
将函数或方法按功能
进行分组;分组之间空2行,方法之间空1行
. - delgate或协议相关方法放到一般内容之后。
- 用
#pragma mark - Lifecycle (生命周期)
- (void)dealloc {}
- (instancetype)init {}
- (void)viewDidLoad {}
- (void)viewWillAppear:(BOOL)animated {}
- (void)didReceiveMemoryWarning {}
#pragma mark - Private (私有方法,比如初始控件设置方法/类内部业务处理方法)
- (CGFloat)setupTableView {}
#pragma mark - Public (对外公开方法)
- (CGFloat)reloadAllData {}
#pragma mark - Network (加载网络数据)
- (void)loadMoreData {}
- (void)loadNewData {}
- (void)loadOtherData {}
#pragma mark - Property Setter/Getter (成员属性的setter和getter方法)
- (void)setCustomProperty:(id)value {}
- (id)customProperty {}
#pragma mark - Events (UIControl响应函数,如按钮点击事件)
- (void)saveButtonClick:(UIButton *)saveButton {}
#pragma mark - KVO/Notification (KVO/通知响应函数)
- (void)dataSourceRefreshNotification:(NSNotification *)notification {}
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSString*, id> *)change context:(nullable void *)context {}
#pragma mark - Protocol/Delegate 如UITableViewDataSource/UITableViewDelegate (协议和代理)
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {}
#pragma mark - LazyLoad (懒加载)
- (UILabel *)nameLabel {}
#pragma mark - Other (其他)
- (id)copyWithZone:(NSZone *)zone {}
- (NSString *)description {}
项目Xode相关配置
代码缩进配置
- 只用
空格缩进
,1个TAB = 4个空格字符
在
XCode->Preferences->Text Editing->Indentation
中进行如下设置:
ps. 设置成4个,是因为Xcode的默认缩进是4个空格。
代码行号显示配置
- Xcode行号显示配置
勾选上
XCode->Preferences->Text Editing->Editing中的Line numbers
,开启行号提示。
项目编码建议
-
建议:每行代码的长度最多
不超过120个字符
;
勾选
XCode->Preferences->Text Editing->Editing
,并将长度设置成120个字符来打开行宽指示。设置成功时Xcode会出现一条竖线! (可选配置
)
建议:为了简洁和便于阅读,请尝试将单个函数或方法的实现代码控制在
50行
内;单个实现文件里的代码行数控制在500~600行
内;建议:为了简洁和便于阅读,请尝试将单个函数或方法的实现代码控制在
50行
内;当接近或超过800行时,就应当开始考虑分割实现文件了。
注释
方法注释
- 方法注释规则
- 注释应该尽量保持
简洁
,代码应该尽量达到能自我解释的程度; - 注释必须和代码保持同步。不要出现代码修改了,注释不更新的情况;
-
对外公开方法 使用
/** 方法描述 */
或VVDocumenter-Xcode
注释,公开方法需尽量注释清楚;如果公开方法有带参数
,可按需对参数进行说明; -
其他方法可使用
// 方法描述
或VVDocumenter-Xcode
注释; - 方法内部
行注释需对齐
;
- 注释应该尽量保持
// .m文件
// 按钮点击事件处理 (非对外公开的方法)
- (void)saveButtonClick:(UIButton *)saveButton {
NSLog(@"Hello Liwx"); // 打印日志
NSLog(@"Hello Liwx"); // 打印日志
}
/** 刷新全部数据 (对外公开的方法)*/
- (void)reloadAllData {
//Do Something
}
// .h文件
/** 刷新全部数据 (对外公开的方法)*/
- (void)reloadAllData;
属性注释
建议
外部属性
注释建议使用/** 属性描述 */
注释, 因为Xcode对此类注释有提示功能,便于开发人员在编码时能更快的了解该属性的作用;建议属性与
@interface ... @end
之间各空1行
;
@interface ViewController ()
/** 时间 */
@property (nonatomic, copy) NSString *time;
@end
- 私有属性注释可使用
// 属性描述
或/** 属性描述 */
注释;
命名与编码规范
类名命名规则
- 类名命名规则
类名、类别名字及协议名字,都采用
大驼峰式命名规则
-
文件名要能反映出它所包含的类的名称
如:NSString.h 和 NSString.m 包含了NSString类的定义和实现
-
Category的文件名要包含它所扩展的那个类的名称,并且类别名称要尽量能够描述它的功能
UIImage+Resize.h 或 UIImage+TintColor.h
-
在面向特定应用的代码中,类名尽量避免使用前缀,每个类都使用相同的前缀会影响可读性
面向特定应用的代码,指那些只会在一个项目中使用的代码,不会被用于其他项目中的代码。
-
在面向多应用的代码中,类名要使用前缀,防止命名冲突
面向多应用的代码,指那些会被多个项目共同使用的代码。
比如CRKit这个类库中,使用了CR前缀。
-
建议:前缀至少使用三个字母
此条是为了减少命名冲突。但鉴于目前流行前缀大多都是两个字母,所以此条不做强制要求
协议编码规则
- 协议编码规则
- 协议声明或定义中,类型标识符、协议名称、尖括号之间
不留空格
;
- 协议声明或定义中,类型标识符、协议名称、尖括号之间
// 协议声明协议名称、尖括号之间不留空格
@protocol UITableViewDelegate<NSObject, UIScrollViewDelegate>
@end
// 定义属性遵守协议,不留空格
@property (nonatomic, weak, nullable) id <UITableViewDelegate> delegate;
控件/控制器定义命名规则
- 控件命名尽量使用控件全称命名,
不建议使用缩写
;命名规则: [功能(name)]+[控件名(Label)]
// 建议使用控件全称命名
/** 名称Label */
@property (nonatomic, weak) UILabel *nameLabel;
/** 保存Button */
@property (nonatomic, weak) UIButton *saveButton;
// 不建议控件名使用缩写
/** 名称Label */
@property (nonatomic, weak) UILabel *nameLbl;
/** 保存Button */
@property (nonatomic, weak) UIButton *saveBtn;
- 定义或声明类时如果名称过长采用
后缀缩写
;注意大小写;- 如控制器
ViewController
可缩写为Vc
; -
TableViewCell
可缩写为TvCell
; -
CollectionViewCell
可缩写为CvCell
;
- 如控制器
// TableViewCell后缀缩写为TvCell
TopicTableViewCell *topicTvCell = nil;
// CollectionViewCell后缀缩写为CvCell
ItemCollectionViewCell *itemCvCell = nil;
// ViewController可缩写为Vc
HomeViewController *homeVc = nil;
方法
方法名和参数命名规则
- 方法名和参数名都采用
小驼峰式命名规则
;
如:- (BOOL)isFileExistedAtPath:(NSString *)filePath;
- 方法名可以使⽤用情态动词(
can
,should
,will
等)来提⾼高清晰性,但不要使⽤用 do 或 does;
// 正确
- (BOOL)canHide;
- (BOOL)shouldRefreshData;
- (void)willChangeData
方法声明和编码规范
- 方法声明中,
-/+
和返回值类型
之间要空1个空格
,方法名
和参数类型
之间以及参数类型和参数名之间不留空格
;
- (instancetype)initwithTitle:(NSString *)title; // 正确
-(instancetype)initwithTitle:(NSString *)title; // 错误
- (instancetype) initwithTitle:(NSString *)title; // 错误
- (instancetype)initwithTitle: (NSString *)title; // 错误
- (instancetype)initwithTitle:(NSString *) title; // 错误
- 方法名和参数名应该尽量读起来像一句话。
如:convertPoint:fromRect: 或者 replaceCharactersInRange:withString:
- 当各个参数是接收者的某个属性时,方法名中不要用
"and"
来连接;
// 正确
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil;
// 错误
- (instancetype)initWithNibName:(NSString *)nibNameOrNil andBundle:(NSBundle *)nibBundleOrNil;
- 方法名之后空
1格
,紧随左大括号{
,无需换号;但是右大括号}
必须另取一行
- (CGFloat)setupTableView {
// Do Something
}
方法调用父类方法编码规则
- 重载父类方法时,遇到必须调用父类方法时。调用super的代码和重载的代码之间留一行空行。将super方法的调用和重载代码区隔开来.
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
// Do Something
}
函数
-
函数指纯C函数,这里提倡与苹果风格类似的约定。
函数名采用大驼峰式
命名方式,``参数名采用小驼峰式`命名方式如果函数和某个特定类型相关,那么函数名前缀要和类型前缀一样
如
CGRectMake(CGFloat x, CGFloat y, CGFloat width, CGFloat height)
等
变量
成员属性编码规则
-
属性名
和变量名
都采用小驼峰式
命名规则; -
禁止使用
匈牙利标记法或含糊不清的缩写单词
来命名变量; - 指针符号
"*" 靠近变量名字
。(常量定义除外) - property属性
括号两边各空1格
, 属性关键字以逗号加空格隔开 -
除了xib拖控件
的方式除外,nonatomic需放在最前面
, strong,weak,assign,copy应放在nonatomic后面; - 成员变量和局部变量声明Demo
// 成员变量声明
/** 标题Label (正确) */
@property (nonatomic, copy) NSString *titleLabel;
/** 标题Label (错误) */
@property (nonatomic, copy) NSString *titleLbl;
/** 标题Label (错误) */
@property(nonatomic, copy) NSString *titleLabel;
/** 标题Label (错误) */
@property (nonatomic,copy) NSString *titleLabel;
/** 标题Label (错误) */
@property (copy, nonatomic) NSString *titleLabel;
// 局部变量声明
NSString *titleLabel = nil; // 正确
NSString* titleLabel = nil; // 错误
NSString * titleLabel = nil; // 错误
NSString*titleLabel = nil; // 错误
- 使用property时,优先使用点语法;
/** 名称 */
@property (nonatomic, copy) NSString *name;
// 访问成员属性时,优先使用点语法
self.name = @"Liwx";
- 赋值操作符
"="
两边各空1格
;
self.name = @"Liwx"; // 正确
self.name= @"Liwx"; // 错误
self.name=@"Liwx"; // 错误
- 如果使用property修饰的是属性BOOL值,建议为getter方法加上一个
"is"开头
的别名。
@property (assign, getter = isSelected) BOOL selected;
- 如果
网络获取的属性
数据为数值型
的,则定义属性也应该为数值型
;如果是枚举类型,则应定义对应枚举类型;不应将数值型定义为NSString字符串类型;
// 正确
@property (copy,nonatomic) UserStatus *userStatus;
// 错误
@property (copy,nonatomic) NSString *userStatus;
常量
Foundation框架常量赋值规则
- 为了提高代码简洁度,创建NSString, NSDictionary, NSArray, 以及NSNumber等常量时,使用Literals语法;
// 正确
NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingZIPCode = @10018;
// 错误
NSArray *names = [NSArray arrayWithObjects:@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul", nil];
NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys: @"Kate", @"iPhone", @"Kamal", @"iPad", @"Bill", @"Mobile Web", nil];
NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES];
NSNumber *zipCode = [NSNumber numberWithInteger:10018];
定义普通常量以字母k开头
- 如果
定义的常量是类
,则格式为:[类名] + [*] + [const] + [k+常量描述]
; - 如果
定义的常量属于基本数据类型
, 则格式为:[基本数据类型] + [const] + [k+常量描述]
;
// 普通常量定义
NSString * const kUserKey = @"kUserKey";
CGFloat const kTopViewHeight = 50;
- 只在某一个特定文件里面使用的常量,用
static
static关键字保证变量只有文件作用
域,可以避免变量名重名
造成的链接错误问题。
如: static CGFloat const RWImageThumbnailHeight = 50.0;
枚举与宏定义
枚举编码规则
- 定义枚举常量时,使用
NS_ENUM
或NS_OPTIONS
;
因为NS_ENUM和NS_OPTIONS都提供了类型检查;
- 定义枚举时,
一定要注释
,并且格式为:[枚举类型名(UITableViewStyle)] + [类型(Plain)]
;
NS_ENUM定义普通枚举
- 使用
NS_ENUM定义普通枚举
Demo,建议
枚举值 = 对应数值
// 应用皮肤样式
typedef NS_ENUM(NSInteger, AppStyle) {
AppStyleLight = 0, // 白天模式
AppStyleDark = 1 // 夜间模式
};
NS_OPTIONS定义位枚举
- 使用
NS_OPTIONS定义位枚举
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
UIViewAutoresizingNone = 0, // 注释
UIViewAutoresizingFlexibleLeftMargin = 1 << 0, // 注释
UIViewAutoresizingFlexibleWidth = 1 << 1, // 注释
...
};
通知和异常
通知NSNotification常量命名规则
- 通知常量命名格式: [相关联的类名字] + [Did | Will] + [独一无二的一段名称] + Notification
-
建议:
定义通知常量不采用宏定义的方法
; - .h文件, 全局通知声明时需加上UIKIT_EXTERN关键字
- .m文件, 通知常量名和值必须保持一致
-
建议:
- 通知命名Demo
// .h文件
// 全局通知声明时需加上UIKIT_EXTERN关键字
UIKIT_EXTERN NSString *const UIKeyboardDidChangeFrameNotification ;
// .m文件, 通知常量名和值必须保持一致
NSString *const UIKeyboardDidChangeFrameNotification = @"UIKeyboardDidChangeFrameNotification";
异常命名规则
- 异常名字的命名规则:[前缀] + [独一无二的一段名称] + Exception
如:NSColorListIOException
布尔值
- Objective-C的布尔值只使用
YES
和NO
; -
注意
:true
和false
只能用于CoreFoundation,C或C++的代码中
; - 禁止将某个值或表达式的结果与YES进行比较
// 正确
if (self.isLogin) {}
// 错误
if (self.isLogin == YES) {}
- 如果返回值为BOOL值,必须确保返回值为YES或NO,最好不要存在多值的情况
// 正确
- (BOOL)isLogin {
return self.userToken.length != 0 ? YES : NO;
}
// 错误
- (BOOL)isLogin {
return self.userToken.length;
}
条件语句
if else 条件语句
- 条件语句的语句体,即便只有一行,也
不能省略花括弧
; - 判断条件之后空
1格
,紧随左大括号{
,无需换号;但是右大括号}
必须另取一行; - else或 elseif应紧随在右大括号
}
之后,并且中间空1格
;
// 正确
if (isLogin) {
// Do Something
} else {
// DO Something
}
// 错误
if (isLogin)
{
// Do Something
}
else {
// DO Something
}
// 正确
if (error == nil) {
return success;
}
// 错误
if (error == nil)
return success;
- 多层嵌套的条件语句,优先考虑条件不成立可以立即跳出的情况
// 优先考虑可以跳出的流程
if (!a) {
return;
}
if (!b) {
return;
}
if (!c) {
return;
}
// 不建议使用嵌套的方式
if (a) {
if (b) {
if (c) {
}
}
}
- 三目运算只有在能增加代码清晰度和整洁度的时候才推荐使用
// 正确
NSInteger value = 5;
result = (value != 0) ? x : y;
// 错误
result = a > b ? x = c > d ? c : d : y;
switch case 语句
-
switch case
如果判断条件是枚举类型
的值,则case也应该为枚举值,而不是 case 1;并且可以省略default
处理
RWTLeftMenuTopItemType menuType = RWTLeftMenuTopItemMain;
switch (menuType) {
case RWTLeftMenuTopItemMain:
// ...
break;
case RWTLeftMenuTopItemShows:
// ...
break;
case RWTLeftMenuTopItemSchedule:
// ...
break;
}
其他规则
初始化方法(构造方法)规则
- 初始化方法的返回类型用
instancetype
,而不是用id
; - 如果重写init方法,必须调用[super init]方法;
直接放回对应数据时,无需添加get
, calc
单词
- (CGFloat)cellHeight; // 正确
- (CGFloat)getCellHeight; // 错误
- (CGFloat)calcCellHeight; // 错误
单例的声明和使用规则
- 获取单例的类方法
+ (instancetype)sharedInstance {
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- UIApplication的使用规则
-
获取单例
的方法不使用点语法
;
-
// 正确
[UIApplication sharedApplication].delegate;
// 错误
UIApplication.sharedApplication.delegate;
设置常用属性,直接使用点语法
// 正确
self.view.backgroundColor = [UIColor redColor];
// 错误
[self.view setBackgroundColor:[UIColor redColor]];
协议和代理方法命名规则
- 协议方法名开头应与类名一样(
不包含前缀
) - 必须将本身作为参数传递给外部,如
(UITableView *)tableView
- 传递所需参数给外部,如
(NSIndexPath *)indexPath
;
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath;
可以使用({})
语法模块化设置控件属性(可选)
- 要使用
({})
语法,前提: 该属性需由strong修饰
;
// 前提: 该属性需由strong修饰
/** 名称Label */
@property (nonatomic, strong) UILabel *nameLabel;
self.nameLabel = ({
UILabel *label = [[UILabel alloc] init];
label.textAlignment = NSTextAlignmentCenter;
label.font = [UIFont systemFontOfSize:12];
label.textColor = [UIColor orangeColor];
label;
});