代码不规范,同事两行泪

任何一个傻瓜都能写出计算机能够理解的代码。惟有写出人类容易理解的代码,才是优秀的程序员。 —— 佚名git

本文是笔者结合公司代码规范要求,和以前看的《禅与 Objective-C 编程艺术》与《Effective Objective-C 2.0》书籍,以及参考相关博客,总结出的一套iOS开发规范。有不足的地方,欢迎博客下留言。程序员

图片发自简书App

大括号

除了 .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];
复制代码

if语句

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; 
复制代码

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
<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)};
复制代码

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 () <UIGestureRecognizerDelegate,
                                  HXSClickEventDelegate,
                                  UITableViewDelegate,
                                  UITableViewDataSource>

复制代码

4. 代理的方法须要明确必须执行和可不执行

  • @required:必须实现的方法
  • @optional:可选是否实现的方法
@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》

相关文章
相关标签/搜索