iOS 代碼規范格式 Objective-C

一、前言

本規范基于Google Objective-C Style Guide,對其中的說明性語句及非ARC部分進行了刪減。每項規范前面的 [強制] 代表該規范需要強制執行,[建議] 代表推薦執行但不強制。

二、縮進與格式

  • 2.1、縮進符
    [強制] 只用空格,用4個空格表示一個縮進。
    選中多行或者一行來使用快捷鍵 control + I 自動縮進

  • 2.2、每行的長度
    [建議] 應盡量控制每行代碼的長度在 120 個字符以內.
    xcode > preferences > text editing > page guide at column: 中將最大行長設置為 80 ,過長的一行代碼將會導致可讀性問題。

  • 2.3、逗號分隔項
    [強制] 用逗號分隔多項時,每個逗號后使用1個空格進行分隔,如下數組,one、two 后面的逗號加空格,以此類推:字典等等都一樣

    NSArray *array = @[@"one", @"two", @"three"];
    
  • 2.4、左大括號位置

    [建議] 左大括號 { 不單獨占據一行,放置在上一行的末尾,可以在 {前增加一個空格,如下

  • 2.5、聲明與定義
    [強制] -、+ 與返回類型之間必須有一個空格,在參數列表中,除了參數之間不要有任何間距,如下

    - (void)doSomethingWithString:(NSString *)string {
        ......
    }
    

    [強制] * 號前面必須要加空格,增加可讀性。
    [建議] 如果有參數太多無法放在同一行內, 最好每個參數各自一行;如果采用多行,每個參數建議按照 : 進行對齊。如下

    - (void)doSomethingWithString1:(NSString *)string1
                           string2:(NSString *)string2
                           string3:(NSString *)string3 {
        ......
    }
    

    [建議] 當第一個關鍵詞比其他的短時,可以縮進之后的行至少4個空格,同樣按 : 進行對齊,如下

    - (void)short:(GTMFoo *)theFoo
            longKeyword:(NSRect)theRect
      evenLongerKeyword:(float)theInterval
                  error:(NSError **)theError {
        ......
    }
    
  • 2.6、方法調用
    [建議] 方法調用的格式須要與定義時的格式一致。當有多種格式化的樣式可供選擇的時候,按照慣例,采用在給定的源文件中使用過的那個方式。
    [建議] 所有的參數都應該在同一行
    舉例如下:

    [myObject doSomethingWithString1:arg1 string2:arg2 string3:arg3];
    

    分析:或者每個參數一行,并按 : 對齊,舉例如下

    [myObject doSomethingWithString1:arg1 
                             string2:arg2 
                             string3:arg3];
    

    不建議采用以下風格,舉例如下,不規范的原因:第一行寫了兩個參數 和 第二行兩個參數

    [myObject doSomethingWithString1:arg1 string2:arg2 
                             string3:arg3];
    [myObject doSomethingWithString1:arg1 
                             string2:arg2 string3:arg3];
    

    拓展:就像聲明和定義一樣,當第一個關鍵詞比其他的短時,可以縮進之后的行至少4個空格,同樣按 : 進行對齊,舉例如下

    [myObj short:arg1
               longKeyword:arg2
         evenLongerKeyword:arg3
                     error:arg4];
    

    補充:調用中如果有包含內聯的 block 也可以縮進至少4個空格,然后左對齊。

  • 2.7、@public、@protected 與 @private

    [強制] 訪問修飾符 @public, @private,@protected必須縮進2個空格。

  • 2.8、異常
    [建議] 在單獨一行時,使用 @ 標簽格式化 exceptions, @ 標簽和左大括號 { 間加一個空格,在 @catch 和 對象捕獲聲明之間也一樣。建議遵循下面的格式

    @try {
         foo();
    }
    @catch (NSException *ex) {
         bar(ex);
    }
    @finally {
         baz();
    }
    
  • 2.9、協議
    [強制] 在類型標識符和封裝在尖括號中的 Protocols 名稱之間要有空格。如下

    @interface MyProtocoledClass : NSObject <NSWindowDelegate> {
    @private
        id <MyFancyDelegate> delegate_;
    }
    - (void)setDelegate:(id <MyFancyDelegate>)aDelegate;
    @end
    
  • 2.10、Blocks

    [強制] 塊內的代碼應縮進4個空格。
    [建議] 因為具體block長度的不同,可以有以下幾種風格:

    • 如果block一行就能放下,就不需要換行

    • 如果必須要換行,那么 } 需要和block所在行的第一個字符對齊
      block中的代碼塊應該縮進4個空格。

    • 如果block很大,比如超過20行,建議單獨拿出來賦給一個本地變量

    • 如果block沒有參數,那字符 ^{ 之間就不應有空格。如果有參數,字符 ^{ 之間同樣沒有空格,但是字符 ) { 之間需要有一個空格。

    • 包含內聯block的函數調用可以在縮進4個空格的基礎上左對齊,尤其是調用中包含多個內聯block。

    • 舉例如下:

      <1>、整個block放在一行的

      [operation setCompletionBlock:^{ [self onOperationDone]; }];
      

      <2>、多行時縮進四個空格,{要和block所在行的第一個字符對齊

      [operation setCompletionBlock:^{
           [self.delegate newDataAvailable];
      }];
      

      <3>、在C函數中使用block時遵循和Objective-C同樣的對齊和縮進原則

      dispatch_async(fileIOQueue_, ^{
           NSString *path = [self sessionFilePath];
           if (path) {
              // ...
           }
      });
      

      <4>、方法參數與block聲明能放到一行時。注意比較^(SessionWindow *window) {和上面的^{。

      [[SessionService sharedService]
           loadWindowWithCompletionBlock:^(SessionWindow *window) {
                 if (window) {
                       [self windowDidLoad:window];
                 } else {
                      [self errorLoadingWindow];
                 }
      }];
      

      <5>、方法參數與block聲明不能放到一行時

      [[SessionService sharedService]
           loadWindowWithCompletionBlock:
               ^(SessionWindow *window) {
                    if (window) {
                        [self windowDidLoad:window];
                    } else {
                        [self errorLoadingWindow];
                    }
      }];
      

      <6>、較長的Block可聲明為變量

      void (^largeBlock)(void) = ^{
            // ...
      };
      [operationQueue_ addOperationWithBlock:largeBlock];
      

      <7>、一次調用中包含多個內聯block

      [myObject doSomethingWith:arg1
           firstBlock:^(Foo *a) {
               // ...
           }
           secondBlock:^(Bar *b) {
               // ...
      }];
      
  • 2.11、Blocks
    [強制] 使用容器(數組和字典)常量,如果其內容被分為多行,應該縮進4個空格。
    [建議] 如果內容單行就能放下,在左大括號之后和右大括號之前各添加一個空格。
    示例:

    NSArray *array = @[ [foo description], @"Another String", [bar description] ];
    NSDictionary *dict = @{ NSForegroundColorAttributeName : [NSColor redColor] };
    

    [建議] 如果內容跨越多行,將左括號和聲明放在同一行,之后換行的內容縮進4個空格,并將右括號單獨放在新的一行里和聲明行對齊。
    示例:

    NSArray *array = @[
         @"This",
         @"is",
         @"an",
         @"array"
    ];
    NSDictionary *dictionary = @{
         NSFontAttributeName : [NSFont fontWithName:@"Helvetica-Bold" size:12],
         NSForegroundColorAttributeName : fontColor
    };
    

    [強制] 對于字典常量,在 : 之前不加空格,之后至少一個空格(或者空格的數量能夠滿足對齊的要求)
    示例:

    NSDictionary *option1 = @{
         NSFontAttributeName: [NSFont fontWithName:@"Helvetica-Bold" size:12],
         NSForegroundColorAttributeName: fontColor
    };
    
    NSDictionary *option2 = @{
         NSFontAttributeName:    [NSFont fontWithName:@"Arial" size:12],
         NSForegroundColorAttributeName: fontColor
    };
    

    下面的都是錯誤的:
    示例

    // :之前應該有空格
    NSDictionary *wrong = @{
          AKey:       @"b",
          BLongerKey: @"c",
    };
    
    // 左大括號之后的內容應該另起一行,或者都放在同一行
    NSDictionary *alsoWrong= @{ AKey : @"a",
                          BLongerKey : @"b" };
    
    // :之前不應該有多個空格
    NSDictionary *stillWrong = @{
         AKey       : @"b",
         BLongerKey : @"c",
    };
    

三、命名規范
在撰寫純粹的Objective-C代碼時,推薦使用駝峰命名法。

  • 3.1、文件名

文件名應該反映其中包含的類實現的名稱,按照項目中的約定且大小寫相關。常見的擴展名如下:

后綴 擴展名
.h C/C++/Objective-C header file
.m Objective-C implementation file
.mm Objective-C++ implementation file
.cc Pure C++ implementation file
.c C implementation file

[強制] 實現Category的文件名需包含類名,如 NSString+Utils.h 或 NSTextView+Autocomplete.h。

  • 3.2、Objective-C++
    在一個源碼文件中, Objective-C++ 遵循你實現的函數/方法的風格。為了最小化在混合開發Cocoa/Objective-C和C++時由命名風格造成的沖突,遵循正在實現方法的風格。如果正在實現的方法是在 @implementation 塊中, 使用Objective-C的命名規范。如果正在實現的方法是在C++的class中,則采用C++的命名規范。
    示例:
    文件: cross_platform_header.h

    class CrossPlatformAPI {
    public:
        ...
        int DoSomethingPlatformSpecific();  // 每個平臺的實現都不一樣
    private:
        int an_instance_var_;
    };
    

    文件: mac_implementation.mm

    #include "cross_platform_header.h"
    
    // 典型的Objective-C class, 使用Objective-C命名規范。
    @interface MyDelegate : NSObject {
       @private
           int _instanceVar;
           CrossPlatformAPI *_backEndObject;
    }
    - (void)respondToSomething:(id)something;
    @end
    @implementation MyDelegate
    - (void)respondToSomething:(id)something {
        // 從Cocoa橋接到C++的后端
        _instanceVar = _backEndObject->DoSomethingPlatformSpecific();
        NSString *tempString = [NSString stringWithFormat:@"%d", _instanceVar];
        NSLog(@"%@", tempString);
    }
    @end
    // C++ class平臺相關的實現, 使用C++命名規范
    int CrossPlatformAPI::DoSomethingPlatformSpecific() {
        NSString *temp_string = [NSString stringWithFormat:@"%d", an_instance_var_];
        NSLog(@"%@", temp_string);
        return [temp_string intValue];
    }
    
  • 3.3、類名
    [強制] 類名(包括Category和Protocol名)以大寫字母開始,通過大小寫而不是下劃線分隔。
    [建議] 在設計可跨越多個應用之間共享的代碼時,推薦使用前綴,(例如,GTMSendMessage)。還有很多外部依賴庫的大型應用中設計的類最好也使用前綴。
    [強制] 若使用前綴,則前綴縮寫要大于2個字符

  • 3.4、Category名
    [強制] 類別(Category)名中需要加入被擴展的類名。比如:比如我們要給NSString類加一個解析的功能,我們創建一個category,命名為GTMStringParsingAdditions,并且放在名為NSString+Parsing.h的文件中。
    [強制] 類名與括號中間需要有一個空格。
    示例
    擴展一個framework類:

    @interface NSString (GTMStringParsingAdditions)
    - (NSString *)foobarString:
    @end
    
    //使方法和屬性私有化
    @interface FoobarViewController ()
    @property(nonatomic, retain) NSView *dongleView;
    - (void)performLayout;
    @end
    
  • 3.5、Objective-C 方法名
    [強制] 方法名應該以小寫字母開頭,混合大小寫。每個命名參數也應該以小寫字母開頭。
    [建議] 方法名稱應該盡可能讀起來像句子,意味著你應該選擇能夠搭配方法名的參數名。(比如:convertPoint:fromRect:或replaceCharactersInRange:withString:)。
    [建議] getter類型的方法不加get前綴。
    示例

    - (id)getDelegate;  // 應避免的
    - (id)delegate;    // 推薦的
    

    [強制] 在所有參數前面添加關鍵字
    示例

     // 推薦的
    - (void)sendAction:(SEL)aSelector toObject:(id)anObject forAllCells:(BOOL)flag; 
    // 錯誤的
    - (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag;  
    

    [建議] 確保參數前面的關鍵字可以正確描述參數
    示例

    // 推薦的
    - (id)viewWithTag:(NSInteger)aTag;
    // 錯誤的
    - (id)taggedView:(int)aTag;       
    

    [強制] 當需要基于已有的一個方法創建新方法時,請將新的關鍵字添加到原有方法后面
    示例

    // 原有方法
    - (id)initWithFrame:(CGRect)frameRect; 
    // 新方法
    - (id)initWithFrame:(NSRect)frameRect mode:(int)aMode cellClass:(Class)factoryId numberOfRows:(int)rowsHigh numberOfColumns:(int)colsWide;  
    

    [建議] 盡量不要使用 and 描述參數
    示例

    // 推薦的
    - (int)runModalForDirectory:(NSString *)path file:(NSString *) name types:(NSArray *)fileTypes;        
    //錯誤的
    - (int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes;  
    

    這些規定適用于Objective-C的方法。C++方法名稱和函數仍沿用C++風格要求。

  • 3.6、變量名
    [強制] 變量名以小寫字母開頭,混合大小寫以區分單詞。

    • 3.6.1、普通變量名
      [建議] 不要使用匈牙利命名法,比如不要使用變量的靜態類型(int 或 pointer)。盡量寫具有描述意義的名稱。
      比如不要使用下面的變量命名,示例

      int w;
      int nerr;
      int nCompConns;
      tix = [[NSMutableArray alloc] init];
      obj = [someObject object];
      p = [network port];
      

      建議使用如下的變量命名:

      int numErrors;
      int numCompletedConnections;
      tickets = [[NSMutableArray alloc] init];
      userInfo = [someObject object];
      port = [network port];
      
    • 3.6.2. 類成員變量
      [建議] 類成員變量的命名是在普通變量的名字前,添加一個下劃線做前綴,比如 _usernameTextField

    • 3.6.3. 常量
      [建議] 常量名(宏,本地常變量等)首字母應該以小寫的k開頭,然后使用混合大小寫區分單詞,枚舉值前面需要以枚舉名為前綴。例如:

      const int kNumberOfFiles = 12;
      NSString *const kUserKey = @"kUserKey";
      enum DisPlayTinge {
          DisplayTingeGreen = 1
          DisplayTingeBlue = 2
      };
      

      總結:變量名應該以小寫字母開頭,并混合大小寫。類的成員變量應該以m_作為前綴。例如:myLocalVariable、m_myInstanceVariable。如果不能使用Objective-C 2.0的@property,使用KVO或者KVC綁定的成員變量可以以一個下劃線作為前綴。

四、注釋

  • 4.1、文件注釋
    [建議] 創建文件時會生成默認的注釋。如果看了文件名還不懂該文件是干什么,可以有選擇的在一個文件開頭寫一段關于內容的描述。

  • 4.2、聲明部分的注釋
    [建議] 每個接口,類別,協議的聲明都應該有個伴隨的注釋,來描述它的作用以及它如何融入整體環境。注釋遵循apple doc風格,以///開始,///結束。
    [強制] 聲明部分的注釋如果以“//”開頭,且與代碼同一行,在 “//” 前加空格。
    示例

    /// <#Description#>
    /// @param nibNameOrNil <#nibNameOrNil description#>
    /// @param nibBundleOrNil <#nibBundleOrNil description#>
    - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil;
    @property (copy) NSString *host; // network host 
    

    [建議] 公共接口的每個方法,都應該有注釋來解釋它的作用、參數、返回值以及其它影響。

  • 4.3、實現部分的注釋
    [強制] 實現部分的注釋如果以“//”開頭,且與代碼同一行,在 “//“ 前加空格。

五、Cocoa 和 Objective-C 特性

  • 5.1、重載指定構造函數
    [強制] 當寫子類的時候,如果需要init...方法,必須重載父類的指定構造函數。

  • 5.2、重載 NSObject 的方法
    [建議] 如果重載了NSObject類的方法,建議把它們放在@implementation內的前邊,這也是慣例。 通常適用(但不局限)于 init...,copyWithZone:,以及dealloc方法。所有init...應放在一起,后面跟著其他NSObject的方法。

  • 5.3、初始化
    [建議] 不要在 init 方法中,將成員變量初始化為 0或 nil。如果一個對象可被復用,狀態需要全部重置,這時可引入 reset方法。
    [強制] 局部變量不會被編譯器初始化,所有局部變量使用前必須初始化。

  • 5.4、避免調用+ new
    [強制] 不要調用NSObject的類方法new,也不要在子類中重載它。使用alloc和init方法創建并初始化對象。

  • 5.5、保持公共 API 簡單
    [建議] 保持公共API簡單,避免“包含一切式“的API。
    示例

    // GTMFoo.m
    #import "GTMFoo.h"
    
    @interface GTMFoo (PrivateDelegateHandling)
    - (NSString *)doSomethingWithDelegate;  // Declare private method
    @end
    
    @implementation GTMFoo(PrivateDelegateHandling)
    ...
    - (NSString *)doSomethingWithDelegate {
        // Implement this method
    }
    ...
    @end
    

    解釋:可以使用私有Category保證公共頭文件整潔
    示例

    @interface GTMFoo () { ... }
    
    • 5.6、#import 與 #include
      [強制] 使用#import來引用 Objective-C/Objective-C++ 頭文件,使用#include引用C/C++頭文件。

    • 5.7、使用根框架
      [強制] 包含根框架,而非單獨的文件。
      示例

      #import <Foundation/Foundation.h> // 推薦
      #import <Foundation/NSArray.h> // 避免
      #import <Foundation/NSString.h> // 避免
      ...
      
    • 5.8、在init和dealloc中避免使用存取方法
      [建議] 在init和dealloc方法執行的過程中,子類可能會處在一個不穩定狀態,所以這些方法中應避免調用存取方法,應在這些方法中直接對成員變量進行賦值或釋放操作。
      示例

      - (instancetype)init {
           self = [super init];
           if (self) {
              _bar = [[NSMutableString alloc] init];  // 推薦
           }
           return self;
      }
      
      - (void)dealloc {
          [_bar release];                           // 推薦
          [super dealloc];
      }
      - (instancetype)init {
          self = [super init];
          if (self) {
             self.bar = [NSMutableString string];  // 避免
          }
          return self;
      }
      
      - (void)dealloc {
          self.bar = nil;                         // 避免
          [super dealloc];
      }
      
  • 5.9、按照聲明順序銷毀實例變量
    [建議] dealloc中對象被釋放的順序應該與他們在 @interface 中聲明的順序一致,這有助于代碼檢查。

  • 5.10、setter中對NSString進行copy
    [建議] 接受NSString作為參數的setter,應該copy它所接受的字符串。
    示例

    - (void)setFoo:(NSString *)aFoo {
       [_foo autorelease];
       _foo = [aFoo copy];
    }
    
  • 5.11、避免拋出異常
    [建議] 不要 @throw Objective-C 異常,但要注意從第三方的調用或者系統調用捕捉異常。如果確實使用了異常,請注釋期望什么方法拋出異常。

  • 5.12、BOOL的使用
    Objective-C 中定義BOOL為無符號字符型,這就意味著BOOL類型可以有不同于 YES(1) 或者 NO(0)的值。不要直接把整形轉換成 BOOL。常見的錯誤包括將數組的大小、指針值以及位運算的結果直接轉換成BOOL,這取決于整型結果的最后一個字節,可能產生一個NO的值。當轉換整型至 BOOL 時,使用三目操作符來返回 YES或者NO。
    [強制] 不要直接將 BOOL 值與 YES 、NO進行比較。
    [建議] 對 BOOL 使用邏輯運算符(&&, || 和 !)是合法的,返回值也可以安全地轉換成 BOOL。
    示例

    - (BOOL)isBold {
        return [self fontTraits] & NSFontBoldTrait;
    }
    
    - (BOOL)isValid {
        return [self stringValue];
    }
    - (BOOL)isBold {
        return ([self fontTraits] & NSFontBoldTrait) ? YES : NO;
    }
    
    - (BOOL)isValid {
        return [self stringValue] != nil;
    }
    
    - (BOOL)isEnabled {
        return [self isValid] && [self isBold];
    }
    

    解釋,同樣,不要直接比較 YES/NO 和 BOOL 變量。不僅僅影響可讀性,結果可能與你想的不同
    示例
    錯誤的寫法

    BOOL great = [foo isGreat];
    if (great == YES) {
         // ...
    }
    

    正確的寫法

    BOOL great = [foo isGreat];
    if (great) {
         // ...be great!
    }
    
  • 5.13、屬性

    • 5.13.1、命名
      [建議] 屬性所關聯的實例變量的命名必須遵守以下劃線作為前綴的規則。屬性的名字應該與成員變量去掉下劃線后綴的名字一模一樣。@property后面緊跟括號,不留空格。
      示例

      @interface MyClass : NSObject
      @property(copy, nonatomic) NSString *name;
      @end
      
      @implementation MyClass
      // No code required for auto-synthesis, else use:
      // @synthesize name = _name;
      @end
      
    • 5.13.2、位置
      [強制] 屬性的聲明必須緊靠著類接口中的實例變量語句塊。屬性的定義必須在 @implementation 的類定義的最上方。他們的縮進與包含他們的 @interface 以及 @implementation 語句一樣。
      示例

      @interface MyClass : NSObject {
          @private
             NSString *_name;
      }
      @property(copy, nonatomic) NSString *name;
      @end
      
      @implementation MyClass
      @synthesize name = _name;
      
      - (instancetype)init {
          ...
      }
      @end
      
    • 5.13.3、字符串應使用 copy 屬性
      [強制] 應總是用 copy 屬性聲明 NSString 屬性。這從邏輯上遵守了 NSString的setter必須使用copy而不是 retain。

  • 5.14、沒有實例變量的接口
    [強制] 沒有聲明任何實例變量的接口,應省略空大括號。
    示例

    @interface MyClass : NSObject
    // Does a lot of stuff
    - (void)fooBarBam;
    @end
    @interface MyClass : NSObject {
    }
    // Does a lot of stuff
    - (void)fooBarBam;
    @end
    
  • 5.15、自動synthesize實例變量
    [建議] 優先考慮使用自動 synthesize 實例變量。
    示例

    // Header file
    @protocol Thingy
    @property(nonatomic, copy) NSString *widgetName;
    @end
    
    @interface Foo : NSObject<Thingy>
    // A guy walks into a bar.
    @property(nonatomic, copy) NSString *bar;
    @end
    
    // Implementation file
    @interface Foo ()
    @property(nonatomic, retain) NSArray *baz;
    @end
    
    @implementation Foo
    @synthesize widgetName = _widgetName;
    @end
    
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容