iOS编码规范
总体命名规则
- 命名原则首先是要顾名思义。命名不要太随意,描述性的命名是最好的。
- 变量名、方法名遵守驼峰命名法,首字母小写。
- 类名、协议名、枚举类型遵守驼峰命名法,首字母大写,这些命名前面均需加前缀,推荐“JM“ + (功能简称)。
- 常量名以k开头,如kJMTest。
- 宏所有字母均大写,命名前面加前缀,推荐”JM“ + (功能简称)。
- 通知Notification的格式 推荐 类/头文件名 + 进行状态(Will | Did) + 通知名称 + Notification
- 协议名使用Delegate做后缀,DataSource使用DataSource做后缀。
头文件
-
声明一个孤立的class或protocol 将声明放入单独的文件,
使头文件名与声明的class/protocol相同。 -
声明关联的class或protocol 将关联的声明(class/category/protocol)放入同一个头文件,头文件名与主要的class/category/protocol相同。
变量
变量尽量以描述性的方式来命名。单个字符的变量命名应该尽量避免,除了在for()循环。
星号表示变量是指针。例如, <font color=#FF69B4>NSString *text
</font>既不是 <font color=#FF69B4>NSString* text
</font> 也不是 <font color=#FF69B4>NSString * text
</font>,除了一些特殊情况下常量。
-
类成员变量
类中所有成员变量以属性的方式提供,不要使用其他类型的变量声明。
通过使用'back'属性(_variable,变量名前面有下划线)直接访问实例变量应该尽量避免,除了在初始化方法 <font color=#FF69B4>(init, initWithCoder:</font>, 等…), <font color=#FF69B4>dealloc</font> 方法和自定义的<font color=#FF69B4>setters</font>和<font color=#FF69B4>getters</font>。如果要使用成员变量,必须以下划线'_'开头,如 NSString *_name。局部变量不应该包含下划线。应该:
@interface Test : NSObject @property (nonatomic, strong) NSString *name; @end
不应该:
@interface Test : NSObject { NSString *_name; }
-
私有属性应该声明在类的.m中,只有需要外部使用的变量才声明在.h中。
-
所有属性特性应该显式地列出来,有助于新手阅读代码。属性特性的顺序应该是atomicity、storage。
应该:
@property (nonatomic, strong) NSString *name;
不应该:
@property (nonatomic) NSString *testName;
-
属性是指针类型的集合时,格式如下:
@property (nonatomic, strong) NSArray <Item *>*array;
-
属性是delegate时,声明成weak,防止循环引用,格式如下:
@property(nonatomic, weak) id<UIScrollViewDelegate> delegate;
常量
常量应该使用static来声明而不是使用#define,除非显式地使用宏。
应该:
static NSString * const kJMAboutViewController = @"JMAboutViewController";
static CGFloat const kRowHeight = 50.0;
不应该:
#define kJMAboutViewController @"JMAboutViewController"
#define kRowHeight 2
布尔值
Objective-C使用的是BOOL值,对应的是YES和NO。true和false是bool类型,Objective-C不要使用。
既然nil解析成NO,所以没有必要在条件语句比较。不要拿某样东西直接与YES比较。
应该:
if (someObject) {
}
if (![anotherObject boolValue]) {
}
不应该:
if (someObject == nil) {}
if ([anotherObject boolValue] == NO) {}
if (isAwesome == YES) {} // Never do this.
if (isAwesome == true) {} // Never do this.
如果BOOL属性的名字是一个形容词,属性就能忽略"is"前缀,但要指定get访问器的惯用名称。例如:
@property (assign, nonatomic, getter=isEditable) BOOL editable;
枚举型
普通枚举型
typedef NS_ENUM(NSInteger,JMTypeTest){
kJMTypeA = 0, // 注释
kJMTypeB = 1, // 注释
kJMTypeC = 2, // 注释
kJMTypeD = 3 // 注释
};
可按位或
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
};
字面值
NSString, NSDictionary, NSArray, 和 NSNumber的字面值应该在创建这些类的不可变实例时被使用。请特别注意nil值不能传入NSArray和NSDictionary字面值,因为这样会导致crash。
应该:
NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
NSDictionary *productManagers = @{@"iPhone": @"Kate", @"iPad": @"Kamal", @"Mobile Web": @"Bill"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingStreetNumber = @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 *buildingStreetNumber = [NSNumber numberWithInteger:10018];
操作符
一元操作符不带空格,多元运算符要带空格隔开。
-
逗号后面都要跟随一个空格。
NSArray *array = @[1, 2, 3, 4];
-
二元操作符,如+, -, *, /,>, <, =, ==, ->, <<, >>等。
a + b = c; a > b; a == b; a << 2; Object -> c;
-
三元操作符 Non-boolean的变量与某东西比较,加上括号()会提高可读性。如果被比较的变量是boolean类型,那么就不需要括号。
应该:
NSInteger value = 5; result = (value != 0) ? x : y; BOOL isHorizontal = YES; result = isHorizontal ? x : y;
不应该:
result = a > b ? x = c > d ? c : d : y; result = a ?: b;
集合类型
- 如果初始值太长,元素需要换行,使⽤四个空格来进行缩进,右括号 ] 或者 } 写在新的⼀行,并且与调⽤语法糖那⾏代码的第一个非空字符对齐。
- 构造字典时,字典的 Key 和 Value与中间的冒号都要留一个空格。
- 需要使用类似”增删改查”方法来对集合进行操作时,方法名前面加上“add”、“remove”、“update”类似表明函数功能的关键字。
NSArray *array = @[
@"This",
@"is",
@"an",
@"array"
];
NSDictionary *dictionary = @{
NSFontAttributeName : [NSFont fontWithName:@"Helvetica-Bold" size:12],
NSForegroundColorAttributeName : fontColor
};
系统保留字
- 系统保留字如if/else、do/while、try/catch 跟()和{}之间都要留一个空格。
- 将关键字与花括号放在一行。
- switch语句每个分支都必须用大括号包起来。
- switch使用枚举型时,如果穷举完所有情况,不能有default分支。其他情况,都必须有default分支。
应该
if (a == 1) {
NSLog(@"test1");
} else if (b == 2) {
NSLog(@"test2");
} else {
NSLog(@"test3");
}
switch (condition) {
case 1:
// ...
break;
case 2: {
// ...
// Multi-line example using braces
}
break;
case 3:
// ...
break;
default:
// ...
break;
}
不应该
if(a==1){
NSLog(@"test1");
}else if(b==2){
NSLog(@"test2");
}else{
NSLog(@"test3");
}
if条件语句
-
条件语句主体为了防止出错应该使用大括号包围,即使只有一行代码。这些错误包括添加第二行代码和期望它成为if语句,但是没包含进大括号;还有,更危险的可能发生在if语句里面一行代码被注释了,然后下一行代码不知不觉地成为if语句的一部分。
应该:
if (!error) { return success; }
不应该:
if (!error) return success;
或
if (!error) return success;
-
不要使用过多的分支,善于使用return来提前返回错误情况,把最正确的情况放到最后返回。
应该
if (!user.UserName) { return NO; } if (!user.Password) { return NO; } if (!user.Email) { return NO; } return YES;
不应该
BOOL isValid = NO; if (user.UserName) { if (user.Password) { if (user.Email) { isValid = YES; } } } return isValid;
-
条件过多,过长的时候应该换行。条件表达式如果很长,则需要将他们提取出来赋给一个BOOL值,或者抽取出一个方法。
应该
if (condition1 && condition2 && condition3 && condition4) { // Do something }
BOOL finalCondition = condition1 && condition2 && condition3 && condition4 if (finalCondition) { // Do something }
if ([self canDelete]) { // Do something } - (BOOL)canDelete{ BOOL finalCondition1 = condition1 && condition2; BOOL finalCondition2 = condition3 && condition4; return condition1 && condition2; }
不应该
if (condition1 && condition2 && condition3 && condition4) { // Do something }
-
if条件语句是和常量比较时,多于3个,使用switch/case实现。
函数声明
- 方法名和参数尽量读起来像是一句话。
- 方法名不允许使用 get、 do 或does 做前缀,动词本身的暗示就够了。
- 如果方法是为了获取对象的一个属性值,直接用属性名称来命名方法,不要添加 get 或其他的动词。
- and 和 with 不应该用于多个参数来说明。
- 方法类型(-/+)和返回值之间要有一个空格。
- 参数类型和指针符之间要留空格。
- 参数类型和冒号之间不需要留空格。
- 参数类型和参数名之间不需要留空格。
应该
- (UIView *)initWithTitle:(NSString *)title backgroundColor:(UIColor *)color;
不应该
-(UIView *)initWithTitle:(NSString *)title withBackgroundColor: (UIColor*) color;
函数实现
- { 和函数名可以在同一行,也可以换行。但是一个类文件里面要保持统一风格。修改者要遵循创建者的风格。
- 函数和函数之间空一行。
- 内部实现时,花括号的嵌套要注意对齐。也可以全部选中,然后使用快捷键control+I。
- 每行建议不超过120个字符,函数名过长时,使用冒号对齐的方式。
- 如果在不同的函数内部有相同的功能,应该把相同的功能抽取出来单独作为另一个函数。
- 将函数内部比较复杂的逻辑提取出来作为单独的函数。
- 尽量避免空方法的产生。
- (void)popBack {
[self.navigationController popViewControllerAnimated:YES];
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
success:(void (^)(NSURLSessionDataTask *, id))success
failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
.m文件整理
- 如果有实例化函数应该放在最前面。
- 如果是view或者viewController的.m文件,应该按照loadView、viewDidLoad、viewWillApperar、viewDidAppear、viewWillDisappear、viewDidDisapper和dealloc的顺序实现,并且这些生命周期相关的函数放在最前面。
- 后面再跟viewWillLayoutSubviews和viewDidLayoutSubviews等这些布局相关的函数。
- setter、getter同类型方法放在一起,并用pragma mark - setter方法 或 pragma mark - getter方法进行标注。
- 相同delegate和datasource的函数放在一起,并用pragma mark - xxx标注。
init方法
Init方法应该遵循Apple生成代码模板的命名规则。返回类型应该使用instancetype而不是id。
- (instancetype)init {
self = [super init];
if (self) {
// ...
}
return self;
}
类构造方法
当类构造方法被使用时,它应该返回类型是instancetype而不是id。这样确保编译器正确地推断结果类型。
@interface Airplane
+ (instancetype)airplaneWithType:(RWTAirplaneType)type;
@end
回调方法
函数调用的可知性,回调时被调用者要知道其调用者,方便信息的传递,所以建议在回调方法第一个参数中加上调用者。除非只有一个名为sender的参数。
如:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
- (BOOL)windowShouldClose:(id)sender;
CGRect函数
当访问CGRect里的x, y, width, 或 height时,应该使用CGGeometry函数而不是直接通过结构体来访问。引用Apple的CGGeometry:
应该:
CGRect frame = self.view.frame;
CGFloat x = CGRectGetMinX(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);
CGRect frame = CGRectMake(0.0, 0.0, width, height);
不应该:
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;
CGRect frame = (CGRect){ .origin = CGPointZero, .size = frame.size };
单例模式
单例对象应该使用线程安全模式来创建共享实例。
+ (instancetype)sharedInstance {
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
Block
- 较短的Block可以写在一行内。
- 如果block过于庞大,应该使用typedef单独声明成一个变量来使用。
typedef void (^AFURLSessionDidBecomeInvalidBlock)(NSURLSession *session, NSError *error);
- 如果分行显示的话,block的右括号应该和调用block那行代码的第一个非空字符对齐。
- block内的代码注意缩进对齐。
- ^ 和左括号 ( 或者 { 之间没有空格,参数列表的右括号 ) 和 {之间有一个空格。
__weak typeof(manager)wManager = manager;
[manager POST:url parameters:reqDic progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[wManager invalidateSessionCancelingTasks:false];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[wManager invalidateSessionCancelingTasks:false];
}];
图片资源命名
图片资源命名方式,以 模块 + 功能 为组织形式,如果相同功能有多个,比如表情,则再加编号。
如:
tabbar_item_1.png
tabbar_item_2.png
maitoutiao_topicon_1.png
maitoutiao_topicon_2.png
注释
- 使用 // 注释时,后面要加一个空格,如果注释+被注释文本过长,使用/*xxx*/。
- 对函数注释时,将光标移到函数行,使用option+command+/快捷键来生成注释。
- 对delegate和dataSource实现时,在第一个函数前面#pragma mark - xxx来进行标注。
警告⚠️
尽量减少⚠️的产生,能修改的要及时修改。