Objective-C代码编写规范
1. 命名规范
我们尽可能遵守Apple的命名约定,其推荐使用长的、描述性强的方法名和变量名,使其阅读起来更加清晰易懂。不能随意使用缩写,导致其他人员阅读代码困难。 附:The Coding Guide for Cocoa
1.1 前缀
项目名称、类名、文件名都应该保持一致的前缀名。根据Apple Guide的建议,类名前缀应该使用2个英文字母以上最好,因为Apple写的框架都是使用2个英文字母开头,使用3个字母能有效防止类名重复,避免影响工程。
1.2 方法命名规范
方法一般不能用init、set
开头进行命名,如果不是写初始化方法不要用init
进行开头;如果不是属性的set
方法,不要用set
作为方法的前缀。
根据Cocoa命名方法规则,我们应该遵守这几个点:
a. 使用小写字母开头,后面嵌套连接的字母使用大写开头。不过在写 Category Method 的时候,我们比较习惯使用 JSD_method 的方式,而非遵守所有的方法命名规则 jsd_method,这个我觉得只要统一起来就好了。
b. 对于采取动作行为的方法,使用动词开头,但是不要直接使用do
或者does
;
c. 每个方法参数前必须带有相同或者能清晰表达其原意的关键字;
d. 子类创建相对父类更加详细功能的方法时应该把新增参数添加在原有方法的后面;
e. 假如方法名过长的时候可以采用每个参数单独占一行的规则,并保持每个参数分号:
对齐的方式排列;
f. 实例方法和类方法 (-/+
) 符号后面应该保持一个空格,如:- (void)
。
1.3 控件命名规范
对于命名一定不要简写,UILabel
结尾加上Label
,UIImageView
结尾加上ImageView
等等让其他人看名字就知道变量的用法和属于什么控件。
1.4 Block 的命名规范
之前研究过很多的第三方命名,对于Apple官方的没找到,有CallBack
、Complete
、Block
结尾的,还有CompletionHandler
结尾的。我看到很多的结尾都是用CompletionHandler
,大部分命名是Block
,我们按照Block
来命名。
例子:
eg 1. 对于#define
宏命名,单词应全部大写,单词之间用_
分割。
建议的写法:
#define NS_AVAILABLE_IOS 3.0
不建议的写法:
#define NSAvailableIos 3.0
eg 2. 类型常量
多用类型常量,少用#define
。
建议的写法:
const NSTimeInterval kAnimationDuration = 0.3;
不建议的写法:
#define ANIMATION_DURATION 0.3
当使用类型常量定义时,若常量局限于某编译单元,也就是实现文件里面,则使用k
作为前缀,如:kCountdownTime
;
若常量在类之外公开出来,则需要使用规定的类名作为前缀。如:SettingCountdownTime
。
约定:在我们自己定义NSNotification
的时候应该把通知的名字定义为一个字符串常量,就像把我们暴露给其他类的字符串常量一样。使用extern
关键字将其在.h
文件声明,并且在.m
文件对其定义。
.h声明:
UIKIT_EXTERN NSString *const BWWillUpdateListNotification;
UIKIT_EXTERN const NSInteger MaxLeadCharCount;
.m实现:
NSString *const BWWillUpdateListNotification = @"kWillUpdateListNotification";
const NSInteger MaxLeadCharCount = 44;
- 关于通知名称,推荐使用
Did
/Will
这样的动词连接表示最好;- 如果导入的是
UIKit
类,就使用UIKIT_EXTERN
;Foundation
类,就使用FOUNDATION_EXTERN
;如果只在本类使用,只用写实现,不用写声明。对于只在内部声明的
const
,需要添加static
,这个我觉得可以不加,但是无法看到苹果的实现,不知道苹果的规范怎么写的。
2. 代码格式
2.1 间距
a. 方法的大括号和其他的大括号(if
/else
/switch
/while
等等)应始终和声明在同一行开始,在新的一行结束;
b. 方法之间应该正好空一行,这样有助于视觉清晰度和代码组织性。在方法中的功能块之间也应该使用空白行分开。
c. switch-case中,case
后的代码如果多于一行,则需要用{}
包裹,建议所有case
和default
后的代码块均用{}
包裹。
建议的写法:
if (user.isHappy) {
// Do something cool
} else {
// Do something else
}
不建议的写法:
if (user.isHappy)
{
// Do something cool
}
else
{
// Do something else
}
2.2 属性关键字首个应该是原子性,再到内存管理关键词。如果需要读写关键字的话,其排在第二位。
建议的写法:
@property (nonatomic, readonly, copy) NSString *name;
2.3 推荐使用三元运算符进行运算,它能使代码更加简洁、清晰。
当三元运算符的第二个参数(也就是if
分支)返回的对象和条件语句中已经检查的对象一样的时候,下面的表达方式更灵巧:
建议的写法:
result = object ? : [self createObject];
不建议的写法:
result = object ? object : [self createObject];
2.4 黄金大道规则(Golden Path)
在使用条件语句编程时,尽管会遇到逻辑复杂的代码,我们也应该尽量避免其嵌套导致阅读困难。
尽量使用return
将不符合逻辑的直接忽略掉,然后将要执行的代码放到判断语句外面,减少嵌套。
建议的写法:
- (void)someMethod {
if (![someOther boolValue]) {
return;
}
// Do something important
}
不建议的写法:
- (void)someMethod {
if ([someOther boolValue]) {
// Do something important
}
}
2.5 避免尤达表达式
不要使用尤达表达式,尤达表达式是指拿一个常量去和变量比较。
建议的写法:
if ([myValue isEqual:@42]) {
}
不建议的写法:
if ([@42 isEqual:myValue]) {
}
3. 文件引入方式
在.h
文件中尽量使用@class
声明文件,直到.m
文件中真正需要的时候再使用#improt
进行引用,这样能有效的防止相互引用、编译失败、不容易查找的bug等;这样做的缺点是:.m
文件还要#import
其他类。
建议的写法:
@class UIView, UIImage;
不建议的写法:
@class UIView;
@class UIImage;
#improt
头文件顺序:可以先引入系统文件,依次到Public.h
,最后才到我们自己编写的文件(可以大家一起商量一下顺序)。记得检查引用文件名称,避免引入了没有使用到的文件,发现后应及时清除。
尽量按照系统类、第三方类、自己写的类顺序导入,中间不能有空行。
写法模板:
#import <系统库>
#import <第三方库>
#import "其他类"
建议的写法:
#import <UIKit/UIKit.h>
#import <Google/Analytics.h>
#import "GBOrderEmptyView.h"
不建议的写法:
#import "GBOrderEmptyView.h"
#import <UIKit/UIKit.h>
#import <Google/Analytics.h>
4. 不允许外界修改的属性要设置 readonly
大家平时设置属性默认是可读可写的,但是这样很容易对别人造成误解,以为可以赋值。因此,对于只能获取的属性,一定写readonly
。
@property (nonatomic, readonly, copy) NSString *name;
5. BOOL 类型属性的声明
属性set
不要带is
,get
要带is
。
建议的写法:
@property (nonatomic, assign, getter=isUserLogin) BOOL userLogin;
不建议的写法:
@property (nonatomic, assign) BOOL userLogin;
6. 对于初始化,一定要使用类对应的初始化方法
a. UIView的对应初始化方法为:- (instancetype)initWithFrame:(CGRect)initWithFrame
b. UIViewController的对应初始化方法为:- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil
防止初始化用init
、new
等,没经过系统进行设置一些默认属性而造成bug。
c. 对于局部变量,尽量进行初始化。局部变量要初始化,属性有默认的值,所以我们不必须对属性进行初始化。
建议的写法:
int index = 0;
不建议的写法:
int index;
7. 对于一些状态、选项,使用枚举,尽量少用数字、字符串判断状态。
建议的写法:
typedef NS_ENUM(NSUInteger, HomeViewState) {
HomeViewStateNoData,
HomeViewStateFailure,
HomeViewStateItemList,
HomeViewStateBannerList,
};
if (state == HomeViewStateNoData) { // 显示无数据
} else if (state == HomeViewStateFailure) { // 显示请求错误
} else if (state == HomeViewStateItemList) { // 显示商品的列表
} else if (state == HomeViewStateBannerList) { // 显示banner列表
}
不建议的写法:
if (state == 0) { // 显示无数据
} else if (state == 1) { // 显示请求错误
} else if (state == 2) { // 显示商品的列表
} else if (state == 3) { // 显示banner列表
}
8. 可变对象的使用
OC存在很多可变的对象,比如:NSMutableString
、NSMutableArray
、NSMutableDictionary
等等,对于一些不允许改变的,直接使用不可变对象,可以节省对象开支,还可以防止别人修改数据造成bug。
建议的写法:
NSArray *sexList = @[@"Man", @"Woman"];
不建议的写法:
NSMutableArray *sexList = [NSMutableArray arrayWithArray:@[@"Man", @"Woman"]];
9. 遍历的写法
a. 如果只需要遍历数组或字典,用forin
。
建议的写法:
for (NSString *name in names) {
// Do something very cool
}
不建议的写法:
for (int i = 0; i < names.length, i++) {
NSString *name = names[i];
}
b. 如果需要遍历数组或字典的内容,并且需要索引时使用enumerator
。
建议的写法:
[names enumerateObjectsUsingBlock:^(NSString * _Nonnull name, NSUInteger idx, BOOL * _Nonnull stop) {
// Do something very cool
}];
不建议的写法:
for (int i = 0; i < names.length, i++) {
NSString *name = names[i];
}
10. 复杂的表达式
建议的写法:
BOOL nameContainsSwift = [sessionName containsString:@"Swift"];
BOOL isCurrentYear = [sessionDateCompontents year] == 2014;
BOOL isSwiftSession = nameContainsSwift && isCurrentYear;
if (isSwiftSession) {
// Do something very cool
}
不建议的写法:
if ([sessionName containsString:@"Swift"] && [sessionDateCompontents year] == 2014) {
// Do something very cool
}
11. NSUserDefaults
的代码使用规范
因为用到NSUserDefaults
无非是保存和读取数据,所以先创建一个对象,可以精简代码,当执行方法很多,用变量替换。
对于我们取值和存值的key要定义一下,定义一下key方便我们使用,并且方便之后改名字。
建议的写法:
NSString *user_id = @"user_id";
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:@"1" forKey:user_id];
不建议的写法:
[[NSUserDefaults standardUserDefaults] setObject:@"1" forKey:@"user_id"];
12. 通知的移除
通知在dealloc
要移除(记得在dealloc
时释放注册的通知和 KVO 的监听)。
建议的写法:
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
不建议的写法:
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self name:name1 object:nil];
}
13. 创建单例的方法
正确的写法:
+ (instancetype)sharedInstance {
static BTSCustomView *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[BTSCustomView alloc] init];
});
return instance;
}
不建议的写法:
+ (instancetype)sharedInstance {
static BTSCustomView *instance = nil;
if (!instance) {
instance = [[BTSCustomView alloc] init];
}
return instance;
}
14. OC方法的代码量
一个方法内部尽量控制在最多50行,如果超过就精简代码,分开方法写,方便之后进行热修复、代码重构;注释一定要写,自己管理的类一定要注释属性用途、方法的用途、参数说明。
a> 属性如果设置默认值,一定要注明默认值是什么;
b> 如果方法内部存在逻辑判断、方法跳转,一定注释逻辑判断的用途、方法跳转的用途;
c> 除了初始化操作,其他声明变量、赋值、判断,应该注释用途。
15. #pragma mark
的使用
对于属性的不同作用,可以进行分组,比如设置颜色的、设置字体的、设置其他样式的;
对于方法的不同作用,可以进行分类,比如添加功能、删除功能的;
对于其他的代理方法、Get
、Set
方法、Init
初始化方法等,可以进行分类。
建议的写法:
#pragma mark - Init
#pragma mark - Request
#pragma mark - Delegate
#pragma mark - DataSource
#pragma mark - Setter
#pragma mark - Getter
16. 注释的写法
<1> 类注释
/**
订单的cell
*/
@interface OrderCell : UITableViewCell
<2> 属性注释
/**
当前订单cell
*/
@property (nonatomic, strong) OrderCell *cell;
<3> 方法注释
/**
显示倒计时文本
@param timerLabel 倒计时文本
@param countTime 剩余时间
@return 是否完成
*/
- (BOOL)timerLabel:(MZTimerLabel *)timerLabel finishedCountDownTimerWithTime:(NSTimeInterval)countTime;
<4> 局部变量和全局变量注释
BOOL _isOfflinePay; // 是否是离线支付
<5> block注释
/**
验证输入的是否正确
@param inputText 输入的文本
@return 如果返回值存在就代表验证失败;否则,就代表成功
*/
typedef NSString *(^ATFVValidateInputCorrectComplete)(NSString *inputText);
<6> 枚举(NS_ENUM
) 注释
/**
当前的状态
- HomeViewStateNoData: 显示无数据
- HomeViewStateFailure: 显示请求错误
- HomeViewStateItemList: 显示商品的列表
- HomeViewStateBannerList: 显示banner列表
*/
typedef NS_ENUM(NSUInteger, HomeViewState) {
HomeViewStateNoData,
HomeViewStateFailure,
HomeViewStateItemList,
HomeViewStateBannerList,
};
17. CGRect
使用
当需要访问CGRect
中某个成员变量时,应该使用CGGeometry
函数来直接访问,而不是使用.
语法来获取。
建议的写法:
CGRect frame = self.view.frame;
CGFloat x = CGRectGetMinX(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);
不建议的写法:
CGRect frame = self.view.frame;
CGFloat x = frame.origin.x;
CGFloat y = frame.origin.y;
CGFloat width = frame.size.width;
CGFloat height = frame.size.height;
18. 错误处理
很多系统方法通过error
返回指针的形式来表示错误,我们应该针对其返回值判断,而非错误变量。
建议的写法:
NSError *error;
if (![self trySomethingWithError:&error]) {
// 处理错误
}
不建议的写法:
NSError *error;
[self trySomethingWithError:&error];
if (error) {
// 处理错误
}
一些苹果API在成功的情况下会写一些垃圾值给错误参数(如果非空),所以针对错误变量可能会造成虚假结果以及接下来的崩溃。
其他
- 下面是整理的一些常用对仗词,大家可以参考使用。
add/delete 添加/删除 add/remove 添加/移除
begin/end 开始/结束
create/destroy 创建/销毁
first/last 第一个/最后一个
get/release 获取/释放 get/set 取出/设置
increment/decrement 增加/减少 insert/delete 插入/删除
lock/unlock 锁/解锁
next/previous 下一个/前一个
old/new 旧的/新的 open/close 打开/关闭
pop/push 出栈/入栈 put/get 放入/取出
send/receive 发送/接收 show/hide 显示/隐藏 source/target 源/目标
source/sink 来源/接收器 source/destination 源/目的地 start/stop 开始/停止
store/query 存储/查询
up/down 向上/向下
visible/invisible 可见/不可见
settings 配置
traversal 遍历
Proactor 设计模式
adapter 适配器
listener 监听器
trigger 触发器
acceptor 接收器
connector 连接器
dispatch 调度/分派/分发
dispatcher 分派器
reactor 反应器
executor 执行器
parser 解析器
builder 生成器/构造器
handle 句柄/处理
handler 处理器
invoke 调用
invoker 调用方
masterplate 模板
- iPhone屏幕适配比例
// 屏幕宽高 宽度 高度
3.5 320x480 100 / 414 * 320 ~= 77.3 100 / 736 * 480 ~= 65.2
4.0 320x568 100 / 414 * 320 ~= 77.3 100 / 736 * 568 ~= 77.2
4.7 375x667 100 / 414 * 375 ~= 90.6 100 / 736 * 667 ~= 90.6
5.5 414x736 100 / 414 * 414 ~= 100 100 / 736 * 736 ~= 100
5.8 375x812 100 / 414 * 375 ~= 90.6 100 / 736 * 812 ~= 110.3
iPhone 6 和 iPhone X 屏幕尺寸对比:
iPhone 6 iPhone X
375x667 375x812
************ ************
* * * *
* * * *
* * * *
* * * *
* * * *
* * * *
* * * *
* * * *
* * * *
* * * *
************ * *
* *
* *
************
宽度相同,高度不同:
667 = 20 + 44 + 554 + 49
812 = 44 + 44 + 641 + 49 + 34
Device Screen Sizes
设备 | 屏幕尺寸 (英寸) | 点分辨率 (pt) | 像素分辨率 (px) | PPI (DPI) | 渲染后 |
---|---|---|---|---|---|
iPhone 3GS | 3.5 | 320 x 480 | 320 x 480 | 163 | 空 |
iPhone 4 / 4s | 3.5 | 320 x 480 | 640 x 960 | 326 | 空 |
iPhone 5 / 5s / SE | 4.0 | 320 x 568 | 640 x 1136 | 326 | 空 |
iPhone 6 / 6s / 7 / 8 | 4.7 | 375 x 667 | 750 x 1334 | 326 | 空 |
iPhone 6 Plus / 6s Plus / 7 Plus / 8 Plus | 5.5 | 414 x 736 | 1242 x 2208 | 401 | 1080 x 1920 |
iPhone X / Xs / 11 Pro | 5.8 | 375 x 812 | 1125 x 2436 | 458 | 空 |
iPhone XR / 11 | 6.1 | 414 x 896 | 828 x 1792 | 326 | 空 |
iPhone Xs Max / 11 Pro Max | 6.5 | 414 x 896 | 1242 x 2688 | 458 | 空 |