任何一个傻瓜都能写出计算机可以理解的代码。唯有写出人类容易理解的代码,才是优秀的程序员。 —— 佚名
本文是笔者结合公司代码规范要求,和之前看的《禅与 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. 对外公开某个常量
如果我们需要发送通知,那么就需要在不同的地方拿到通知的“频道”字符串(通知的名称),那么显然这个字符串是不能被轻易更改,而且可以在不同的地方获取。这个时候就需要定义一个外界可见的字符串常量。
推荐:
//.hextern NSString *const ZOCCacheControllerDidClearCacheNotification;复制代码
//.mstatic 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//子类ZOCDaySalesListViewControllerZOCMonthSalesListViewController复制代码
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复制代码
可以使用代码块一键生成,参考
相等性的判断
判断两个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
icon_tab_bookshelf_sel@2x.png复制代码
面试题(风格纠错)
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复制代码