任何一个傻瓜都能写出计算机可以理解的代码。唯有写出人类容易理解的代码,才是优秀的程序员。 —— 佚名
本文是笔者结合公司代码规范要求,和之前看的《禅与 Objective-C 编程艺术》与《Effective Objective-C 2.0》书籍,以及参考相关博客,总结出的一套iOS开发规范。有不足的地方,欢迎博客下留言。
大括号
除了 .m 文件中方法,其他的地方大括号"{"不需要另起一行。推荐:
if (!error) {
return success;
}
- (void)doHomework
{
if (self.hungry) {
return;
}
//doSomething
}
运算符
1. 一元运算符与变量之间没有空格:
!aValue
-aValue //负号
~aValue //位非
++iCount
*strSource
2. 二元运算符与变量之间必须有空格
fWidth = 5 + 5;
fLength = fWidth * 2;
for(int i = 0; i < 10; i++)
3.三元运算符
当三元运算符的第二个参数(if 分支)返回和条件语句中已经检查的对象一样的对象的时候,下面的表达方式更灵巧:
result = object ? : [self createObject];
不推荐:
result = object ? object : [self createObject];
if语句
1. 尽量列出所有的情况,且给出明确的结果。
推荐:
var hintStr;
if (count < 3) {
hintStr = "Good";
} else {
hintStr = "";
}
2. 黄金大道
在使用条件语句编程时,代码的左边距应该是一条“黄金”或者“快乐”的大道,也就是说善于使用return来提前返回不符合的情况。
推荐:
- (void)someMethod {
if (![someOther boolValue]) {
return;
}
// Do something important
}
3. 复杂的表达式
条件表达式如果比较复杂,则需要将他们提取出来赋给一个BOOL变量。
推荐:
BOOL nameContainsSwift = [sessionName containsString:@"Swift"];
BOOL isCurrentYear = [sessionDateCompontents year] == 2019;
BOOL isSwiftSession = nameContainsSwift && isCurrentYear;
if (isSwiftSession) {
// Do something very cool
}
4.尤达表达式
尤达表达式是指,拿一个常量去和变量比较而不是拿变量去和常量比较。
推荐:
if (count == 6) {
}
if (myValue == nil) {
}
if (!object ) {
}
不推荐:
if ( 6 == count) {
}
if ( nil == object ) {
}
5. 条件语句体应该总是被大括号包围
尽管有时候你可以不使用大括号(比如,条件语句体只有一行内容),但是这样做会带来问题隐患。
推荐:
if (!error) {
return success;
}
不推荐:
if (!error)
return success;
if (!error) return success;
Switch语句
1. 每个分支都必须用大括号括起来
推荐:
switch (integer) {
case 1: {
// ...
break;
}
case 2: {
// ...
break;
}
case 3: {
// ...
break;
}
default:{
// ...
break;
}
}
2.除了使用枚举类型以外,都必须有default分支
switch (menuType) {
case menuTypeLeft: {
// ...
break;
}
case menuTypeRight: {
// ...
break;
}
case menuTypeTop: {
// ...
break;
}
case menuTypeBottom: {
// ...
break;
}
}
在Switch语句使用枚举类型的时候,如果使用了default分支,在将来就无法通过编译器来检查新增的枚举类型了。
函数
1. 一个函数的长度尽量限制在50行以内
如果一个方法里面的代码行数过多,代码的阅读体验极差。
2. 一个函数只做一件事(单一原则)
每个函数的职责都应该划分的很明确(就像类一样)。
3. 对于有返回值的函数,确保每个分支都有返回值
推荐:
int function()
{
if(condition1){
return count1
}else if(condition2){
return count2
}else{
return defaultCount
}
}
4. 外部传入的参数需要检验参数的非空、数据类型的合法性,参数错误立即返回或断言
推荐:
void function(param1,param2)
{
if(!param1){
return;
}
if(!param2){
return;
}
//Do some right thing
}
5. 多个函数如果有逻辑重复的代码,建议将重复的部分抽取出来,成为独立的函数进行调用
6. 如果方法参数过多过长,建议多行书写,每个参数占用一行,用冒号进行对齐
推荐:
- (void)initWithAge:(NSInteger)age
name:(NSString *)name
weight:(CGFloat)weight;
7. 方法名中不应使用and,而且签名要与对应的参数名保持一致
推荐:
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;
不推荐:
- (instancetype)initWithWidth:(CGFloat)width andHeight:(CGFloat)height;
注释
1.类的注释
对于类的注释写在当前类文件的顶部。
2.属性注释
对于属性的注释建议写在属性上面,用的时候,会有提示功能。
/// 刷新按钮
@property (nonatomic, strong) UIButton *refreshBtn;
3.方法注释
对于.h文件中方法的注释,通过快捷键command+option+/
快速注释;
对于.m文件中方法的注释,在方法的上边添加//
,注释符和注释内容需要间隔一个空格。例如
// load network data
4.功能注释
版本迭代中,在同事写的代码基础上开发,一定要写上版本功能注释,方便询问具体功能。推荐
// 这是一个新加的功能 v5.20.0 by minjing.lin
变量
1. 变量名必须使用驼峰格式
类,协议使用大驼峰:
HomePageViewController.h
对象等局部变量使用小驼峰:
NSString *personName = @"";
NSUInteger totalCount = 0;
2.变量的名称必须同时包含功能与类型
UIButton *addBtn
UILabel *nameLbl
NSString *addressStr
3. 系统常用类作实例变量声明时加入后缀
类型 | 后缀 |
---|---|
UIViewController | VC |
UIView | View |
UILabel | Lbl |
UIButton | Btn |
UIImage | Img |
UIImageView | ImagView |
NSArray | Arr |
NSMutableArray | Marr |
NSDictionary | Dict |
NSMutableDictionary | Mdict |
NSString | Str |
NSMutableString | Mstr |
NSSet | Set |
NSMutableSet | Mset |
常量
1. 常量以相关类名作为前缀
推荐:
static const NSTimeInterval ZOCSignInViewControllerFadeOutAnimationDuration = 0.4;
不推荐:
static const NSTimeInterval fadeOutTime = 0.4;
2. 建议使用类型常量,不建议使用#define预处理命令
首先比较一下这两种声明常量的区别:
- 预处理命令:简单的文本替换,不包括类型信息,并且可被任意修改。
- 类型常量:包括类型信息,并且可以设置其使用范围,而且不可被修改。
推荐:
static const CGFloat ZOCImageThumbnailHeight = 50.0f;
不推荐:
#define CompanyName @"Apple Inc."
#define magicNumber 42
3. 对外公开某个常量
如果我们需要发送通知,那么就需要在不同的地方拿到通知的“频道”字符串(通知的名称),那么显然这个字符串是不能被轻易更改,而且可以在不同的地方获取。这个时候就需要定义一个外界可见的字符串常量。
推荐:
//.h
extern NSString *const ZOCCacheControllerDidClearCacheNotification;
//.m
static NSString * const ZOCCacheControllerDidClearCacheNotification = @"ZOCCacheControllerDidClearCacheNotification";
宏
1. 字母全部大写,单词与单词之间用_
分割
#define URL_GAIN_QUOTE_LIST @"/v1/quote/list"
#define URL_UPDATE_QUOTE_LIST @"/v1/quote/update"
2. 宏定义中如果包含表达式或变量,表达式和变量必须用小括号括起来
#define MY_MIN(A, B) ((A)>(B)?(B):(A))
枚举
当使用 enum 的时候,建议使用新的固定的基础类型定义,因为它有更强大的类型检查和代码补全。
typedef NS_ENUM(NSInteger, UIControlContentVerticalAlignment) {
UIControlContentVerticalAlignmentCenter = 0,
UIControlContentVerticalAlignmentTop = 1,
UIControlContentVerticalAlignmentBottom = 2,
UIControlContentVerticalAlignmentFill = 3,
};
范型
建议在定义NSArray和NSDictionary时使用泛型,可以保证程序的安全性:
NSArray *testArr =@[@"hello",@"world"];
NSDictionary *dic = @{@"key":@(1), @"age":@(10)};
NSMutableArray
1. addObject之前要非空判断。
2. 取下标的时候要判断是否越界。
3. 取第一个元素或最后一个元素的时候使用firtstObject和lastObject
字面量语法
尽量使用字面量值来创建 NSString , NSDictionary , NSArray , NSNumber 这些不可变对象:
推荐:
NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve"];
NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingZIPCode = @10018;
不推荐:
NSArray *names = [NSArray arrayWithObjects:@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", nil];
NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys: @"Kate", @"iPhone", @"Kamal", @"iPad", @"Bill" ];
NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES];NSNumber *buildingZIPCode = [NSNumber numberWithInteger:10018];
Block
为常用的Block类型创建typedef
如果我们需要重复创建某种block(相同参数,返回值)的变量,我们就可以通过typedef来给某一种块定义属于它自己的新类型。
例如:
int (^variableName)(BOOL flag, int value) =^(BOOL flag, int value)
{
// Implementation
return someInt;
}
这个Block有一个bool参数和一个int参数,并返回int类型。我们可以给它定义类型:
typedef int(^EOCSomeBlock)(BOOL flag, int value);
再次定义的时候,就可以通过简单的赋值来实现:
EOCSomeBlock block = ^(BOOL flag, int value){
// Implementation
};
定义作为参数的Block:
- (void)startWithCompletionHandler: (void(^)(NSData *data, NSError *error))completion;
这里的Block有一个NSData参数,一个NSError参数并没有返回值
typedef void(^EOCCompletionHandler)(NSData *data, NSError *error);
- (void)startWithCompletionHandler:(EOCCompletionHandler)completion;”
通过typedef定义Block签名的好处是:如果要某种块增加参数,那么只修改定义签名的那行代码即可。
属性
1.书写规则
@property、空格、括号、线程修饰词、内存修饰词、读写修饰词、空格、类、对象名称;
根据不同的场景选择合适的修饰符。
推荐:
@property (nonatomic, copy, readonly) NSString *name;
@property (nonatomic, strong, readwrite) UIView *headerView;
@property (nonatomic, weak) id<#delegate#> delegate;
2. Block属性应该使用copy关键字
推荐:
typedef void (^ErrorCodeBlock) (id errorCode,NSString *message);
@property (nonatomic, copy) ErrorCodeBlock errorBlock;
@property (nonatomic, copy) <#returnType#> (^<#Block#>)(<#parType#>);
3. 形容词性的BOOL属性的getter应该加上is前缀
推荐:
@property (nonatomic, assign, getter=isEditable) BOOL editable;
4. 对外尽量使用不可变对象
尽量把对外公布出来的属性设置为只读,在实现文件内部设为读写。具体做法是:
- 在头文件中,设置对象属性为
readonly
。 - 在实现文件中设置为
readwrite
。
这样一来,在外部就只能读取该数据,而不能修改它,使得这个类的实例所持有的数据更加安全。而且,对于集合类的对象,更应该仔细考虑是否可以将其设为可变的。
如果在公开部分只能设置其为只读属性,那么就在非公开部分存储一个可变型。所以当在外部获取这个属性时,获取的只是内部可变型的一个不可变版本,例如:
在公共API中:
@interface EOCPerson : NSObject
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
@property (nonatomic, strong, readonly) NSSet *friends //向外公开的不可变集合
- (id)initWithFirstName:(NSString*)firstName andLastName:(NSString*)lastName;
- (void)addFriend:(EOCPerson*)person;
- (void)removeFriend:(EOCPerson*)person;
@end
在这里,我们将friends属性设置为不可变的set。然后,提供了来增加和删除这个set里的元素的公共接口。
在实现文件里:
@interface EOCPerson ()
@property (nonatomic, copy, readwrite) NSString *firstName;
@property (nonatomic, copy, readwrite) NSString *lastName;
@end
@implementation EOCPerson {
NSMutableSet *_internalFriends; //实现文件里的可变集合
}
- (NSSet*)friends
{
return [_internalFriends copy]; //get方法返回的永远是可变set的不可变型
}
- (void)addFriend:(EOCPerson*)person
{
[_internalFriends addObject:person]; //在外部增加集合元素的操作
//do something when add element
}
- (void)removeFriend:(EOCPerson*)person
{
[_internalFriends removeObject:person]; //在外部移除元素的操作
//do something when remove element
}
- (id)initWithFirstName:(NSString*)firstName andLastName:(NSString*)lastName
{
if ((self = [super init])) {
_firstName = firstName;
_lastName = lastName;
_internalFriends = [NSMutableSet new];
}
return self;
}
我们可以看到,在实现文件里,保存一个可变set来记录外部的增删操作。
这里最重要的代码是:
- (NSSet*)friends
{
return [_internalFriends copy];
}
这个是friends属性的获取方法:它将当前保存的可变set复制了一不可变的set并返回。因此,外部读取到的set都将是不可变的版本。
代理方法
1. 代理方法的第一个参数必须为委托者
代理方法必须以委托者作为第一个参数(参考UITableViewDelegate)的方法。其目的是为了区分不同委托着的实例。因为同一个控制器是可以作为多个tableview的代理的。例如:
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
2.向代理发送消息时需要判断其是否实现该方法
推荐:
if ([self.delegate respondsToSelector:@selector(signUpViewControllerDidPressSignUpButton:)]) {
[self.delegate signUpViewControllerDidPressSignUpButton:self];
}
3. 遵循代理过多的时候,换行对齐显示
推荐:
@interface ShopViewController ()
4. 代理的方法需要明确必须执行和可不执行
- @required:必须实现的方法
- @optional:可选是否实现的方法
@protocol ZOCServiceDelegate
@optional
- (void)generalService:(ZOCGeneralService *)service didRetrieveEntries:(NSArray *)entries;
@end
类
1. 类的名称
应该以三个大写字母为前缀;创建子类的时候,应该把代表子类特点的部分放在前缀和父类名的中间。
推荐:
//父类
ZOCSalesListViewController
//子类
ZOCDaySalesListViewController
ZOCMonthSalesListViewController
2. 所有返回类对象和实例对象的方法都应该使用instancetype
将instancetype关键字作为返回值的时候,可以让编译器进行类型检查,同时适用于子类的检查,这样就保证了返回类型的正确性(一定为当前的类对象或实例对象)
推荐:
- (instancetype)init
{
self = [super init]; // call the designated initializer
if (self) {
// Custom initialization
}
return self;
}
@interface ZOCPerson
+ (instancetype)personWithName:(NSString *)name;
@end
不推荐:
@interface ZOCPerson
+ (id)personWithName:(NSString *)name;
@end
3. 在类的.h
文件中尽量少引用其他头文件
有时,类A需要将类B的实例变量作为它公共API的属性。这个时候,我们不应该引入类B的头文件,而应该使用向前声明(forward declaring)使用class关键字,并且在A的实现文件引用B的头文件。
// EOCPerson.h
#import
@class EOCEmployer;
@interface EOCPerson : NSObject
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
@property (nonatomic, strong) EOCEmployer *employer;//将EOCEmployer作为属性
@end
// EOCPerson.m
#import "EOCEmployer.h"
优点:
不在A的头文件中引入B的头文件,就不会一并引入B的全部内容,这样就减少了编译时间。
可以避免循环引用:因为如果两个类在自己的头文件中都引入了对方的头文件,那么就会导致其中一个类无法被正确编译。
但是个别的时候,必须在头文件中引入其他类的头文件:
- 该类继承于某个类,则应该引入父类的头文件。
- 该类遵从某个协议,则应该引入该协议的头文件。而且最好将协议单独放在一个头文件中。
4. 类的布局
#pragma mark - Life Cycle Methods
- (instancetype)init
- (void)dealloc
- (void)viewWillAppear:(BOOL)animated
- (void)viewDidAppear:(BOOL)animated
- (void)viewWillDisappear:(BOOL)animated
- (void)viewDidDisappear:(BOOL)animated
#pragma mark - Override Methods
#pragma mark - Network Methods
#pragma mark - Target Methods
#pragma mark - Public Methods
#pragma mark - Private Methods
#pragma mark - UITableViewDataSource
#pragma mark - UITableViewDelegate
#pragma mark - Setters and Getters
可以使用代码块一键生成,参考Xcode 快速开发 代码块
相等性的判断
判断两个person类是否相等的合理做法:
- (BOOL)isEqual:(id)object
{
if (self == object) {
return YES; //判断内存地址
}
if (![object isKindOfClass:[ZOCPerson class]]) {
return NO; //是否为当前类或派生类
}
return [self isEqualToPerson:(ZOCPerson *)object];
}
//自定义的判断相等性的方法
- (BOOL)isEqualToPerson:(Person *)person
{
if (!person) {
return NO;
}
BOOL namesMatch = (!self.name && !person.name) || [self.name isEqualToString:person.name];
BOOL birthdaysMatch = (!self.birthday && !person.birthday) || [self.birthday isEqualToDate:person.birthday];
return haveEqualNames && haveEqualBirthdays;
}
图片命名
1.命名规范:不能有中文、大写、特殊符号、空白
2.命名格式(推荐):
fileType
[function]project
[pageName]imageName
[status]{.png,@2x.png,@3x.png}
万能公式:类别_功能_模块_页面_名称_状态.png
[email protected]
面试题(风格纠错)
typedef enum{
UserSex_Man,
UserSex_Woman
}UserSex;
@interface UserModel :NSObject
@property(nonatomic, strong) NSString *name;
@property (assign,nonatomic) int age;
@property (nonatomic,assign) UserSex sex;
-(id)initUserModelWithUserName: (NSString*)name withAge(int age);
-(void)doLogIn;
@end
参考文献:
禅与 Objective-C 编程艺术
iOS 代码规范
看完这个你们团队的代码也很规范
《Effective Objective-C 2.0》