任何一个傻瓜都能写出计算机能够理解的代码。惟有写出人类容易理解的代码,才是优秀的程序员。 —— 佚名git
本文是笔者结合公司代码规范要求,和以前看的《禅与 Objective-C 编程艺术》与《Effective Objective-C 2.0》书籍,以及参考相关博客,总结出的一套iOS开发规范。有不足的地方,欢迎博客下留言。程序员
除了 .m 文件中方法,其余的地方大括号"{"不须要另起一行。推荐:github
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];
复制代码
不推荐:bash
result = object ? object : [self createObject];
复制代码
1. 尽可能列出全部的状况,且给出明确的结果。session
推荐:app
var hintStr;
if (count < 3) {
hintStr = "Good";
} else {
hintStr = "";
}
复制代码
2. 黄金大道
在使用条件语句编程时,代码的左边距应该是一条“黄金”或者“快乐”的大道,也就是说善于使用return来提早返回不符合的状况。ide
推荐:
- (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;
复制代码
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
<HeaderViewDelegate>
复制代码
对象等局部变量使用小驼峰:
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<NSString *> *testArr =@[@"hello",@"world"];
NSDictionary<NSString *, NSNumber *> *dic = @{@"key":@(1), @"age":@(10)};
复制代码
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类型建立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 () <UIGestureRecognizerDelegate,
HXSClickEventDelegate,
UITableViewDelegate,
UITableViewDataSource>
复制代码
4. 代理的方法须要明确必须执行和可不执行
@protocol ZOCServiceDelegate <NSObject>
@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 <Foundation/Foundation.h>
@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
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
复制代码
禅与 Objective-C 编程艺术
iOS 代码规范
看完这个大家团队的代码也很规范 《Effective Objective-C 2.0》