美文网首页
iOS代码规范

iOS代码规范

作者: Jessica124 | 来源:发表于2020-04-27 09:36 被阅读0次

    代码规范

    一些原则

    1. 首先是为人编写程序,其次才是计算机

    软件的生命周期贯穿产品的开发,测试,生产,用户使用,版本升级和后期维护等过程,只有易读,易维护的软件代码才具有生命力。

    2. 见名知意

    长的,描述性的方法和变量命名是好的。不要使用简写,除非是一些大家都知道的场景比如 VIP。不要使用 bgView,推荐使用 backgroundView。含义清楚、代码自我表述能力强,做好可不加注释。(前提是代码足够规范)。

    3. 保持代码的简明清晰,避免过分的编程技巧

    简单是最美。不要过分追求技巧,否则会降低程序的可读性。

    4. 编程时首先达到正确性,其次考虑效率

    编程首先考虑的是满足正确性,健壮性,可维护性,可移植性等质量因素。

    5. 编写代码时需要考虑到代码的可测试性

    不可以测试的代码是无法保障质量的。实现设计功能的同时,要提供可以测试、验证的方法。

    6. 函数(方法)是为一特定功能而编写,不是万能工具箱

    方法是一个处理单元,是由特定功能的,所以应该很好地规划方法,不能是所有东西都放在一个方法里实现。

    7. 鼓励多注释

    当代码有改动时,注释一定要修改,并且不要添加多余或重复的注释。

    8. 删除没必要的代码

    比如我们新建一个控制器,里面会有一些不会用到的代码,或者注释起来的代码,如果这些代码不需要,那就删除它。


    布局

    程序布局的目的是现实出程序良好的逻辑结构,提高程序的准确性、连续性、可读性、可维护性。统一的程序布局和编程风格,有助于提高整个项目的开发质量,提高开发效率,降低开发成本

    遵循统一的布局顺序书写头文件和实现文件:

    1. #import “LocationCustomButton.h”

    最开始是导入需要使用到的其他class头文件,头文件按类型分类

    // controller
    #import “TextSelectionViewController.h"
    // model
    #import "PayPasswordUpdateModel.h"
    #import "WalletModel.h"
    // views
    #import "PayPasswordAlertView.h"
    

    2. #define KEY_BANK_TAIL @“bank_tail"

    添加宏定义,将在文件中需要使用到的常量,字符串等用宏定义。

    常量定义

    3. @property (nonatomic, strong) NSArray *bankCardList;

    property的属性定义。不用将需要使用的methods声明,在iOS7后Methods已经不需要先声明再使用了。

    4. - (void)viewDidLoad

    #pragma mark - Life Cycle 添加生命周期的方法

    - (void)viewWillAppear:(BOOL)animated
    - (void)viewDidAppear:(BOOL)animated
    - (void)viewWillDisappear:(BOOL)animated
    - (void)viewDidDisappear:(BOOL)animated
    - (void)didReceiveMemoryWarning
    - (void)dealloc
    

    5. #pragma mark - override

    重载父类的方法

    6. #pragma mark - Intial Methods

    初始化的方法,如

    - (void)initScriber
    - (void)initSubviews
    

    7. #pragma mark - Target Methods

    点击事件或通知事件

    8. #pragma mark - UITextFieldDelegate

    Delegate的事件,将所有的delegate放在同一个pragma下

    9. #pragma mark - Setter Getter Methods

    所有的property使用懒加载,并将setter或getter放在底部,不影响业务代码的阅读。

    预览如下:

    #import "ViewController.h"
    // controller
    #import “TextSelectionViewController.h"
    // model
    #import "PayPasswordUpdateModel.h"
    #import "WalletModel.h"
    // views
    #import "PayPasswordAlertView.h"
    //other
    
    #define KEY_BANK_TAIL @"bank_tail"
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    #pragma mark - life cycle
      
    - (void)viewWillAppear:(BOOL)animated
    - (void)viewDidAppear:(BOOL)animated
    - (void)viewWillDisappear:(BOOL)animated
    - (void)viewDidDisappear:(BOOL)animated
    - (void)didReceiveMemoryWarning
    - (void)dealloc
    
    #pragma mark - override
    
    #pragma mark - Intial Methods
    
    - (void)initScriber
    - (void)initSubviews
    
    #pragma mark - Target Methods
    
    #pragma mark - UITableViewDelegate
    
    #pragma mark - UITableViewDataSource
    //...(多个代理方法依次往下写)
    
    #pragma mark - getters and setters
    
    @end
    

    可以用代码块方式快速生成,参考 https://www.jianshu.com/p/e5609cf43a4f


    命名

    1. 标识符要采用英文单词或其组合,便于记忆和阅读,切忌使用汉语拼音来命名。

    标识符应当直观且可以拼读,可望文知意,英文单词一般不要太复杂,用词应当准确。

    2. 严格禁止使用连续的下划线,下划线也不能出现在标识符头或结尾。(实例变量及特殊用法除外)

    CGFloat variable__name;
    NSString *variale___name;
    

    3. 程序中不要出现仅靠大小区分的相似的标识符。

    NSString *contentOfView;
    NSString *ContentOfView;
    

    4. 宏、常量名都要使用大写字母,用下划线‘_’分割单词。

    #define URL_GAIN_QUOTE_LIST @"/v1/quote/list"
    #define URL_UPDATE_QUOTE_LIST @"/v1/quote/update"
    #define URL_LOGIN   @"/v1/user/login”
    

    以 K 开头。后面遵循大写驼峰命名。「不带参数」

    #define KHomePageDidScroll @"com.xq.home.page.tableview.did.scroll"
    

    5. 程序中局部变量不要与全局变量重名。

    尽管局部变量和全局变量的作用域不同而不会发生语法错误,但容易使人误解。

    6. 方法名用小写字母开头的单词组合而成。

    方法名力求清晰、明了、通过方法名就能够判断方法的主要功能。方法名中不同意义字段之间不要用下划线连接,而要把每个字段的首字母大写以示区分。

    - (NSString *)descriptionWithLocale:(id)locale;
    

    7. 尽量避免名字中出现数字编号,如Value1, Vlaue2等,除非逻辑上的确需要编号。


    大括号

    除了 .m 文件中方法,其他的地方大括号"{"不需要另起一行。推荐:

    - (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; 
    

    6. 当有条件过多、过长的时候需要换行,为了代码看起来整齐些

    推荐:

    if (condition1() && 
        condition2() && 
        condition3() && 
        condition4()) {
      // Do something
    }
    

    不推荐:

    if (condition1() && condition2() && condition3() && condition4()) { // Do something }
    

    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. 一个函数的长度尽量限制在200行以内
    如果一个方法里面的代码行数过多,代码的阅读体验极差。

    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;
    

    如果方法名比参数名短,则每个参数占用一行,至少缩进4个字符,且为垂直对齐(非冒号对齐)。

    - (void)short:(NSString *)theFoot
        longKeyword:(CGRect)theRect
        evenLongerkeyword:(float)theInterval
    

    !方法调用沿用声明方法的习惯:在同一行或者冒号对齐。

    7. 方法名中不应使用and,而且签名要与对应的参数名保持一致

    推荐:

    - (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;
    

    不推荐:

    - (instancetype)initWithWidth:(CGFloat)width andHeight:(CGFloat)height;
    

    8. 函数(方法)块之间使用一个空行分隔

    9. 关键字之后要留空格。“(”后和“)”前 不添加空格

    if、for、while等关键字之后应留一个空格再跟左括号”(”。

    if (fWidth == 0)
    

    10. 方法名与形参不能留空格,返回类型与方法标识符有一个空格

    方法名后紧跟”:”,然后紧跟形参,返回类型”(”与”-”之间有一个空格。

    - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
    

    11. 在接口中应该尽量少使用外部定义的类型(减少耦合)
    12. 避免函数有太多的参数,参数个数尽量控制在5个以内
    如果参数的确比较多,不妨把这些参数定义成一个结构(或一个类)。

    13. 禁止直接调用NSObject的类方法+new,也不要在子类中重载它。使用alloc和init方法

    14. Dealloc的顺序要与变量声明的顺序相同

    这样有利于review代码。
    如果dealloc中调用其他方法来release变量,将被release的变量以注释的形式标注清楚。
    先release自身成员变量,再调用父类dealloc方法。


    变量

    1. 变量名必须使用驼峰格式
    类,协议使用大驼峰:

    HomePageViewController.h
    <HeaderViewDelegate>
    

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

    NSString *personName = @"";
    NSUInteger totalCount = 0;
    

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

    UIButton *addBtn 
    UILabel  *nameLbl 
    NSString *addressStr
    

    3. 一个变量最好只有一个作用,切勿为了节省代码行数,觉得一个变量可以做多个用途。(单一原则)

    4. 方法内部如果有局部变量,那么局部变量应该靠近在使用的地方,而不是全部在顶部声明全部的局部变量。

    5. 每个类型的命名以该类型结尾

    • ViewController:使用 ViewController 结尾。例子:ApplyRecordsViewController
    • View:使用 View 结尾。例子:分界线:boundaryView
    • NSArray:使用 s 结尾。比如商品分类数据源。categories
    • UITableViewCell:使用 Cell 结尾。比如 MyProfileCell
    • Protocol:使用 Delegate 或者 Datasource 结尾。比如 XQScanViewDelegate
    • Tool:工具类
    • 代理类:Delegate
    • Service 类:Service

    常量

    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. 以 K 开头。后面遵循大写驼峰命名。「不带参数」

    #define KHomePageDidScroll @"com.xq.home.page.tableview.did.scroll"
    

    3. 宏定义中如果包含表达式或变量,表达式和变量必须用小括号括起来

    #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)};
    

    字面量语法

    尽量使用字面量值来创建 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

    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"
    

    表达式

    表达式是语句的一部分,他们是不可分割的。

    1. 一条语句只完成一个功能。

    复杂的语句阅读起来,难于理解,并容易隐含错误。

    2. 在表达式中使用括号,使表达式的运算顺序更清晰。

    由于将运算符的优先级与结合律熟记是比较困难的,为了防止产生歧义并提高可读性,即使不加括号时运算顺序不会改变,也应当用括号确定表达式的操作顺序。

    if ( (( 0 == iYear%4 ) && ( 0 != iYear%100 )) || ( 0 == iYear%400 ) )
    

    3. 避免表达式中的附加功能,不要编写太复杂的复合表达式。

    不推荐:
    int iResult = iYear++-++iMonth+iDay++;

    4. 不可将布尔变量和逻辑表达式直接与YES、NO或1、0进行比较。

    if (isSuccess)   //真
    if (!isSuccess)  //假
    

    5. 在条件判断语句中,当整型变量与0比较时,不可模仿布尔变量的风格,应当将整型变量用“==”或“!=”直接与0比较。

    if (iYear == 0)
    if ( iMonth  != 0 )
    

    6. 应当将指针变量用“==”或“!=”与nil比较。

    指针变量的零值是“空”(即nil),nil的值与0相同,但是两者含义不同。

    if ( strName  == nil)
    

    7. 在switch语句中,每一个case分支必须使用break结尾,最后一个分支必须是default分支。

    避免漏掉break语句造成程序错误,同时保持程序简洁。对于多个分支相同处理的情况可以共用一个break,但是要用注释加以说明。

    8. 不可在for循环内修改循环变量,防止for循环失去控制。

    9. 循环嵌套次数不大于3次。
    10. do while 语句和while语句仅使用一个条件。

    11. 如果循环体内存在逻辑判断,并且循环次数很大,宜将逻辑判断移到循环体的外面。

    12. 将int值转换为BOOL时应特别小心。
    OC中,BOOL被定义为unsigned char,这意味着除了YES(1)和NO(0)外它还可以是其他值。禁止将int直接转换为BOOL。

    将整型值转换为BOOL的方法:使用三元运算符返回YES/NO,或使用&&,||。


    注释

    1.类的注释
    对于类的注释写在当前类文件的顶部。

    2.属性注释
    对于属性的注释建议写在属性上面,用的时候,会有提示功能。

    /// 刷新按钮
    @property (nonatomic, strong) UIButton *refreshBtn;
    

    3.方法注释
    对于.h文件中方法的注释,通过快捷键command+option+/快速注释; 对于.m文件中方法的注释,在方法的上边添加//,注释符和注释内容需要间隔一个空格。例如

    // load network data
    

    4.功能注释
    版本迭代中,在同事写的代码基础上开发,一定要写上版本功能注释,方便询问具体功能。推荐

    // 这是一个新加的功能 v5.20.0 by minjing.lin
    

    5. 类中功能模块以#pragma mark – 分割,上空两行,下空一行


    其他

    1. 避免过多直接使用magic number(魔法数字) 。

    应该都使用宏定义,采用立即数不容易理解含义并容易出错。

    2. 枚举第一个成员要赋初始值

    3. addObject之前要非空判断

    4. release版本代码去掉NSLog打印,除了保留异常分支的NSLog

    5. 禁止在代码中直接写死字符串资源,必须要用字符串ID替代

    应该考虑多语言国际化,尽量使用NSLocalizedStringFromTable实现对字符串ID的引用。

    6. 使用空格将相同的变量、属性对齐,使用换行分组


    参考文章:

    https://juejin.im/post/5c8f9dd2f265da611846a177
    http://www.cocoachina.com/articles/26491
    https://www.jianshu.com/p/08be5b30ff82
    https://github.com/FantasticLBP/codesnippets

    相关文章

      网友评论

          本文标题:iOS代码规范

          本文链接:https://www.haomeiwen.com/subject/mdwewhtx.html