代码不规范,同事两行泪

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

本文是笔者结合公司代码规范要求,和之前看的《禅与 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
<HeaderViewDelegate>
复制代码

对象等局部变量使用小驼峰:

NSString *personName = @"";
NSUInteger totalCount = 0;
复制代码

2.变量的名称必须同时包含功能与类型

UIButton *addBtn 
UILabel *nameLbl 
NSString *addressStr
复制代码

3. 系统常用类作实例变量声明时加入后缀

类型后缀
UIViewControllerVC
UIViewView
UILabelLbl
UIButtonBtn
UIImageImg
UIImageViewImagView
NSArrayArr
NSMutableArrayMarr
NSDictionaryDict
NSMutableDictionaryMdict
NSStringStr
NSMutableStringMstr
NSSetSet
NSMutableSetMset

常量

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》

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值