本文主要翻譯自蘋果官方文檔,翻譯不當之處希望issue
github

一、代碼命名規範

本節主要介紹類、方法、函數、常量以及編程接口的其他元素的命名規範。應該在項目中嚴格遵守本準則。

一般原則

  • 1.清晰:在命名時盡量做到清晰簡潔,如果清晰和簡潔相互制約時,應當以清晰為首。
代碼 結論
insertObject:atIndex: 正確
insert:at: 錯誤。插入什麼?在哪裡插入?
removeObjectAtIndex: 正確
removeObject: 正確
remove: 錯誤。什麼被刪除?

命名時應當使用全稱,而不是簡寫。

代碼 結論
destinationSelection 正確
destSel 錯誤。
setBackgroundColor: 正確
setBkgdColor: 錯誤

不使用簡寫並不是說完全不能使用,對於某些國際通用的簡寫是可以使用的。點這裏
儘管做到這些,我們還是無法保證對一些命名的清晰度,那麼就應該使用註釋來加以說明。

  • 2.一致性:在不同類中執行相同操作的方法應或屬性該具有相同的名稱。
代碼 結論
@property(nonatomic) NSInteger tag; 定義在UIView,NSView等中,表示視圖標籤。

前綴

Objective-C中,由於沒有命名空間的說法,使得前綴變得尤為重要。它們區分軟件的功能區域,防止開發人員之間或與系統的命名重合。一般情況下前綴會和所屬框架緊密集合,命名類、協議、函數、常量和typedef結構時使用前綴,方法命名時一般不需要添加前綴,除非你非要加。

前綴 結論
NS Foundation、Application Kit
AB Address Book
IB Interface Builder

駝峰法則

  • 1.方法命名時,盡量不使用前綴。以小寫字母開頭,此後每個單詞的首字母大寫。

fileExistsAtPath:isDirectory:

  • 2.常量、類、協議以前綴大寫開頭,此後每個單詞首字母大寫。
NSRunAlertPanel
NSCellDisabled

類和協議名稱

  • 1.類的名稱應當清除的包涵該類代表什麼或做什麼的名稱,並且使用恰當的類前綴。

UIView,NSString,NSDate,NSScanner,NSApplication,UIApplication,NSButton和UIButton

  • 2.協議表示一類方法的集合,與類有本質上的區別。為了區分類和協議,協議命名時盡量使用一些恰當的動詞來命名:NSLocking
  • 3.有部分協議只是組合了一些不相關的方法,通常這些方法將作用於具體某一個類的,這時候建議將協議名和類名進行映射,甚至一樣:NSObject

頭文件

  • 1.頭文件命名盡量保證能夠清楚知曉內部所包含的內容和適當的前綴。
  • 2.如果是框架類型的,需要添加一個包含所有依賴關係的頭文件。

二、方法命名

通用規則

  • 1.方法以小寫字母開頭,此後每個單詞的首字母大寫。別使用前綴。有兩個例外的是:

1.類似於PDF、TIFF、HTTP等這種固有名稱大寫來開頭一個方法。
2.可以使用前綴來分組和標識私有方法。

  • 2.若方法為表示對對象的某種操作,需要使用該操作的動詞起頭。不要使用“do”或“does”作為名稱的一部分,因為這些輔助動詞很少添加意義。 另外,在動詞前不要使用副詞或形容詞
- (void)invokeWithTarget:(id)target;
- (void)selectTabViewItem:(NSTabViewItem *)tabViewItem
  • 3.如果該方法返回接收方的屬性,請在該屬性後面指定該方法。 使用get是不必要的,除非間接返回一個或多個值。
代碼 結論
- (NSSize)cellSize; 正確
- (NSSize)calcCellSize; 錯誤。
- (NSSize)getCellSize; 錯誤。
  • 4.多個參數時,善於利用關鍵字進行區分。
代碼 結論
- (void)sendAction:(SEL)aSelector toObject:(id)anObject forAllCells:(BOOL)flag; 正確
- (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag; 錯誤
  • 5.在參數前使用關於該參數的描述名稱加以修飾。
代碼 結論
- (id)viewWithTag:(NSInteger)aTag; 正確
- (id)taggedView:(int)aTag; 錯誤
  • 6.在創建比繼承的方法更具體的方法時,將新關鍵字添加到現有方法的末尾。
代碼 結論
- (id)initWithFrame:(CGRect)frameRect; NSView, UIView.
- (id)initWithFrame:(NSRect)frameRect mode:(int)aMode cellClass:(Class)factoryId numberOfRows:(int)rowsHigh numberOfColumns:(int)colsWide; NSMatrix, a subclass of NSView
  • 7.不要使用and鏈接兩個參數。
代碼 結論
- (int)runModalForDirectory:(NSString *)path file:(NSString *) name types:(NSArray *)fileTypes; NSView, UIView.
- (id)initWithFrame:(NSRect)frameRect mode:(int)aMode cellClass:(Class)factoryId numberOfRows:(int)rowsHigh numberOfColumns:(int)colsWide; NSMatrix, a subclass of NSView
  • 8.如果該方法描述兩個單獨的操作,這是可以使用and鏈接它們。
代碼 結論
- (BOOL)openFile:(NSString *)fullPath withApplication:(NSString *)appName andDeactivate:(BOOL)flag; NSWorkspace

存取方法

  • 1.如果屬性用名詞表示,則格式為:
- (type)noun;
- (void)setNoun:(type)aNoun;
  • 2.如果屬性用形容詞表示,則格式為:
- (type)isAdjective;
- (void)setAdjective:(type)flag;
  • 2.如果屬性用動詞表示,則格式為:
- (type)verbObject;
- (void)setVerbObject:(type)flag;
  • 3.您可以使用模態動詞(動詞前面加上can,should,will等)來澄清意思,但不要使用do,does

代理方法

委託方法(或委託方法)是當某些事件發生時,委託對象在其委託中調用。

  • 1.使用發送該委託消息的對象的類來作為開頭命名。類名省略前綴,第一個字母是小寫。
- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row;
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename;
  • 2.如果有多個參數並且需要引用調用對象時,第一個參數應當為引用本身,直接在類名后添加。否則,只有一個調用對象需要引用時應當盡量清晰的表明參數的使用。
- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row;
- (BOOL)applicationOpenUntitledFile:(NSApplication *)sender;
  • 3.發布通知而調用的方法,唯一的參數是通知對象。
- (void)windowDidChangeScreen:(NSNotification *)notification;
  • 4.對於被調用的方法,使用didwill通知委託人已經發生或將要發生的事情。
- (void)browserDidScroll:(NSBrowser *)sender;
- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window;
  • 5.雖然您可以使用did”或“will來表示方法的調用時間順序,但是當你建議委託對象執行某些操作時,should是首選項。
- (BOOL)windowShouldClose:(id)sender;

集合對象

當對象需要對集合類的對象(arrays,dictionaries,sets)進行管理時,需要遵守以下規則:

- (void)addElement:(elementType)anObj;
- (void)removeElement:(elementType)anObj;
- (NSArray *)elements;
  • 1.如果集合是真正無序的,則返回NSSet對象而不是NSArray對象。
  • 2.如果將元素插入集合中的特定位置很重要,請使用與以下內容類似的方法:
- (void)insertLayoutManager:(NSLayoutManager *)obj atIndex:(int)index;
- (void)removeLayoutManagerAtIndex:(int)index;

方法參數

  • 1.遵守駝峰原則。
  • 2.讓參數類型而不是其名稱聲明它是否是一個指針。
  • 3.為參數避免使用一個和兩個字母的名稱。
  • 4.避免只保留幾個字母的縮寫。
    舉個例子,以下關鍵字和參數一起使用:
...action:(SEL)aSelector
...alignment:(int)mode
...atIndex:(int)index
...content:(NSRect)aRect
...doubleValue:(double)aDouble
...floatValue:(float)aFloat
...font:(NSFont *)fontObj
...frame:(NSRect)frameRect
...intValue:(int)anInt
...keyEquivalent:(NSString *)charCode
...length:(int)numBytes
...point:(NSPoint)aPoint
...stringValue:(NSString *)aString
...tag:(int)anInt
...target:(id)anObject
...title:(NSString *)aString

私有方法

在私用方法命名是和上面提到的大相徑庭,不同的是你可以使用前綴來區分。Cocoa框架中大多數私有方法的名稱都有一個下劃線前綴(例如_fooData),將其標記為私有。 從這個事實可以看出兩個建議:

  • 1.如果您將一個大型Cocoa框架類(如NSView或UIView)進行子類化,並且您希望絕對確保您的私有方法的名稱與超類中的名稱不同,則可以為您的私有方法添加自己的前綴。 前綴應盡可能獨一無二,也許根據您的公司或項目以及XX_的格式。 所以如果您的項目稱為Byte Flogger,則前綴可能為BF_addObject:
  • 2.在使用下劃線字符作為您的私有方法的前綴時請謹慎,官方有這個習慣。

函數命名

Objective-C允許您通過函數和方法來表達行為。底層對象始終是單例時,您應該使用函數而不是類方法。

  • 1.函數名稱類似於方法命名,但有一些例外:
  • 他們的前綴和作用於的類或常量相同。
  • 若沒有在前綴後面跟隨下劃線時,就應當保證駝峰原則了。
  • 2.大多數函數名稱以描述函數的效果的動詞開頭。
NSHighlightRect
NSDeallocateObject
  • 3.查詢屬性的函數還有一組命名規則:
  • 如果函數返回其第一個參數的屬性,則省略動詞。

      unsigned int NSEventMaskFromType(NSEventType type)
      float NSHeight(NSRect aRect)
  • 如果通過引用返回值,請使用Get

      const char *NSGetSizeAndAlignment(const char *typePtr, unsigned int *sizep, unsigned int *alignp)
  • 如果返回的值是一個布爾值,則該函數應以一個變形的動詞開頭。

      BOOL NSDecimalIsNotANumber(const NSDecimal *decimal)

屬性和數據類型的命名

本節介紹聲明屬性,實例變量,常量,通知和異常的命名約定。

屬性和實例變量的命名

  • 1.屬性只是集變量、存取器於一體的操作,基本也和存取器的命名一致。通用情況下聲明屬性模板為:
@property (…) type nounOrVerb;
  • 2.很多情況下,不僅會聲明一個屬性,還是聲明一個與之對應的實例變量。確保實例變量的名稱簡明扼要地描述了存儲的屬性。 通常,你不應該直接訪問實例變量; 您應該使用訪問器方法(您可以直接在init和dealloc方法中訪問實例變量)。 為了有助於表明這一點,前綴實例變量名稱帶有下劃線(_),例如:
@implementation MyClass {
    BOOL _showsTitle;
}
  • 3.如果聲明的屬性和實例變量需要一一對應時,請使用@synthesize來指定:
@implementation MyClass
@synthesize showsTitle = _showsTitle;
  • 4.聲明實例變量時需要注意一下幾個問題:
  • 避免顯式聲明公共實例變量。
  • 如果需要聲明實例變量,請使用@private或@protected顯式聲明它.
  • 如果實例變量是類的實例的可訪問屬性,請確保為其編寫訪問器方法(如果可能,請使用聲明的屬性)。

常量

  • 1.枚舉常量
  • 對具有整數值的相關常量的組使用枚舉。
  • 枚舉常數和它們分組的typedef遵循函數的命名約定(參見函數命名)。以下示例來自NSMatrix.h:\

      typedef enum _NSMatrixMode {
          NSRadioModeMatrix           = 0,
          NSHighlightModeMatrix       = 1,
          NSListModeMatrix            = 2,
             NSTrackModeMatrix           = 3
      } NSMatrixMode;
  • 可以為位掩碼創建未命名的枚舉,例如:

      enum {
          NSBorderlessWindowMask      = 0,
          NSTitledWindowMask          = 1 << 0,
          NSClosableWindowMask        = 1 << 1,
          NSMiniaturizableWindowMask  = 1 << 2,
          NSResizableWindowMask       = 1 << 3
      };
  • 2.常量
  • 如果常數與其他常量無關,則可以使用const創建整數常量;否則,使用枚舉。
  • 常量的格式以下列聲明為例:

      const float NSLightGray;
  • 與枚舉常量一樣,命名約定與函數相同。
  • 3.其他
  • 對於整數常量,使用枚舉,而浮點常量使用const限定符。盡量不適用#define
  • 使用大寫字母作為預處理程序評估的符號,以確定是否處理代碼塊。 例如:

      #ifdef DEBUG
  • 請注意,編譯器定義的宏具有前導和後置雙下劃線字符。 例如:

      __MACH__
  • 為用於通知名稱和字典鍵等用途的字符串定義常量。 通過使用字符串常量,您確保編譯器驗證指定了正確的值(即,它執行拼寫檢查)。 Cocoa框架提供了許多字符串常量的例子,例如(對於Objective-C,APPKIT_EXTERN宏對外部進行評估):

      APPKIT_EXTERN NSString *NSPrintCopies;

通知和異常

  • 1.通知
  • 通知名稱應當清晰的知曉通知所做的事情,通知事件應當和通知名稱相應一致。 例如,當應用程序發布NSApplicationDidBecomeActiveNotification時,全局NSApplication對象的委託將自動註冊以接收applicationDidBecomeActive:消息。
  • 通知通過以這種方式組成名稱的全局NSString對象來標識:

      [Name of associated class] + [Did | Will] + [UniquePartOfName] + Notification
  • 例如:

      NSApplicationDidBecomeActiveNotification
      NSWindowDidMiniaturizeNotification
      NSTextViewDidChangeSelectionNotification
      NSColorPanelColorDidChangeNotification
  • 2.異常
  • 異常由全局NSString對象識別,其名稱以這種方式組成:

      [Prefix] + [UniquePartOfName] + Exception
  • 例如:

      NSColorListIOException
      NSColorListNotEditableException
      NSDraggingException
      NSFontUnavailableException
      NSIllegalSelectorException

框架開發技巧

初始化

  • 1.類的初始化
  • 在調用類之前需要對其做一些初始社會,並且只需要執行一次時,你可以使用+ (void)initialize方法。不能自行調用initialize。因為運行時向每個類發送初始化,所以可能在子類的上下文中調用initialize,如果子類不實現initialize,那麼調用將落入超類。如果您特別需要在相關類的上下文中執行初始化,則可以執行以下檢查:

>

```
if (self == [NSFoo class]) {
// the initializing code
}
```
  • 2.指定初始化器
  • 指定的初始值設置應該被清楚地識別,並且參數所表述的信息也應當十分清晰。當你的類需要執行歸檔時,你必須實現initWithCoder:encodeWithCoder:
  • 3.初始化時檢查錯誤
  • 通過調用super的指定的初始化程序來加載父類的實現。
  • 檢查返回的值為nil,表示超類初始化中發生一些錯誤。
  • 如果在初始化當前類時發生錯誤,請釋放該對象並返回nil。

      - (id)init {
          self = [super init];  // Call a designated initializer here.
          if (self != nil) {
              // Initialize object  ...
              if (someError) {
                  [self release];
                  self = nil;
              }
          }
          return self;
      }

版本和兼容性

  • 1.框架版本
  • 版本號中記錄更改情況。
  • 設置您的框架的當前版本號,並提供一些方法使其可全局訪問。您可以將版本號存儲在框架的信息屬性列表(Info.plist)中,並從中進行訪問。
  • 2.密匙歸檔
  • 如果存檔中缺少密鑰,請求其值將返回nilNULLNO00.0,具體取決於所請求的類型。測試此返回值以減少您寫出的數據。 另外,您可以確定是否將密鑰寫入存檔。
  • 編碼和解碼方法都可以做到這一點,以確保向後兼容。例如,類的新版本的編碼方法可能會使用密鑰寫入新值,但是仍然可以寫出較舊的字段,以便舊版本的類仍然可以理解該對象。此外,解碼方法可能希望以合理的方式處理缺少的值,以便為將來的版本保持一定的靈活性.
  • 框架類的歸檔密鑰的推薦命名是從框架的其他API元素使用的前綴開始,然後使用實例變量的名稱。只要確保名稱不能與任何超類或子類的名稱衝突。
  • 請確保使用唯一的鍵為需要保存的密匙的映射值。例如,歸檔矩形的“archiveRect”例程應該採用一個關鍵參數,並使用給定的鍵,或者如果它寫出多個值(例如,四個浮點數),它應該將附加的唯一位添加到提供的鍵。
  • 由於編譯器和字節依賴關係,原來存檔的位域可能是危險的。

錯誤和異常

大多數Cocoa框架方法不會強制開發人員捕獲和處理異常。 這是因為異常不會作為正常的執行部分提出,通常不用於傳達預期的運行時或用戶錯誤。 這些錯誤的例子包括:

  • 文件為找到
  • 無使用者
  • 嘗試在應用程序中打開錯誤類型的文檔
  • 將字符串轉換為指定的編碼時出錯

但是,Cocoa確實引發了異常以指示編程或邏輯錯誤,如下所示

  • 數組索引超出界限
  • 嘗試改變不可變對象
  • 錯誤的參數類型

Cocoa方法一般不會返回錯誤代碼。在有一個合理或可能的錯誤原因的情況下,這些方法依賴於布爾值或對象(nil / non-nil)返回值的簡單測試;記錄NO或NIL返回值的原因。您不應該使用錯誤代碼來指示在運行時處理的編程錯誤,而是引發異常,或者在某些情況下只需記錄錯誤而不會引發異常。

框架數據

  • 1.常量數據
  • 出於性能原因,盡可能多地將框架數據標記為不變,因為這樣做可以減小Mach-O二進制數的DATA段的大小。不是const的全局和靜態數據最終在DATA段的DATA部分。這種數據在使用該框架的應用程序的每個運行實例中佔用內存。雖然額外的500字節(例如)可能看起來不太糟糕,但可能會導致需要的頁數增加,每個應用程序額外增加四千字節。您應該將任何常量的數據標記為const。如果塊中沒有char *指針,這將導致數據降落在TEXT段中(這使得它真正不變);否則它將保留在DATA段中,但不會被寫入(除非預綁定未完成,否則必須在加載時滑動二進制文件)。您應該初始化靜態變量,以確保它們被合併到DATA段的data部分,而不是bss部分。如果沒有明顯的值用於初始化,請使用0,NULL,0.0或任何適當的值。
  • 2.為字段
  • 如果代碼假定該值為一個布爾值,則使用位域(特別是一位)的帶符號值可能會導致未定義的行為。一位位域應始終為無符號。因為可以存儲在這樣的位域中的唯一值為0和-1(取決於編譯器的實現),將該位域與1進行比較是false。 例如,如果您在代碼中遇到這樣的問題:

      BOOL isAttachment:1;
      int startTracking:1;

    你應當把類型更改為:unsigned int

  • 3.內存分配
  • 如果由於某種原因需要臨時緩衝區,通常使用堆棧比分配緩衝區更好。但是,堆棧的大小有限(通常為512千字節),因此決定使用堆棧取決於所需的緩衝區的功能和大小。通常,如果緩衝區大小為1000字節(或MAXPATHLEN)或更小,則使用堆棧是可以接受的。
  • 使用堆棧和malloc開闢空間:

      #define STACKBUFSIZE (1000 / sizeof(YourElementType))
       YourElementType stackBuffer[STACKBUFSIZE];
       YourElementType *buf = stackBuffer;
       int capacity = STACKBUFSIZE;  // In terms of YourElementType
       int numElements = 0;  // In terms of YourElementType
      while (1) {
          if (numElements > capacity) {  // Need more room
              int newCapacity = capacity * 2;  // Or whatever your growth algorithm is
              if (buf == stackBuffer) {  // Previously using stack; switch to allocated memory
                  buf = malloc(newCapacity * sizeof(YourElementType));
                  memmove(buf, stackBuffer, capacity * sizeof(YourElementType));
              } else {  // Was already using malloc; simply realloc
                  buf = realloc(buf, newCapacity * sizeof(YourElementType));
              }
              capacity = newCapacity;
          }
          // ... use buf; increment numElements ...
        }
        // ...
        if (buf != stackBuffer) free(buf);

對象比較

  • 1.一般都需要獨立一個比較方法當類中,用於比較兩個對象是否相同。
  • 2.嚴格區分isEqual:isEqualToType:

三、其他

代碼結構

在方法分組和protocol/delegate實現中使用#pragma mark -來分類方法,要遵循以下一般結構:

#pragma mark - Properties

- (void)setCustomProperty:(id)value {}

#pragma mark - Lifecycle

- (instancetype)init {}
- (void)dealloc {}
- (void)viewDidLoad {}
- (void)viewWillAppear:(BOOL)animated {}
- (void)didReceiveMemoryWarning {}

#pragma mark - Custom Accessors

- (void)setCustomProperty:(id)value {}
- (id)customProperty {}

#pragma mark - IBActions

- (IBAction)submitData:(id)sender {}

#pragma mark - Public

- (void)publicMethod {}

#pragma mark - Private

- (void)privateMethod {}

#pragma mark - Protocol conformance
#pragma mark - UITextFieldDelegate
#pragma mark - UITableViewDataSource
#pragma mark - UITableViewDelegate

#pragma mark - NSCopying

- (id)copyWithZone:(NSZone *)zone {}

#pragma mark - NSObject

- (NSString *)description {}

空格

  • 1.確保使用Xcode默認的間距。
  • 2.方法大括號和其他大括號(if/else/switch/while 等。),總是在同一行語句打開但在新行中關閉。

      if (user.isHappy) {  
          //Do something  
      } else {  
          //Do something else  
      }
  • 3.應該避免以冒號對齊的方式來調用方法。因為有時方法簽名可能有3個以上的冒號和冒號對齊會使代碼更加易讀。請不要這樣做,儘管冒號對齊的方法包含代碼塊,因為Xcode的對齊方式令它難以辨認。

      // blocks are easily readable  
      [UIView animateWithDuration:1.0 animations:^{  
            // something  
      } completion:^(BOOL finished) {  
            // something  
      }];
  • 4.應盡量在你的代碼中將每行控制在100個字符內。

註釋

  • 1.一般應當盡量避免塊註釋,盡量讓每一行代碼都自行註釋。
  • 2.方法註釋使用系統提供的快捷鍵進行註釋即可(`command+shift+`)。

屬性特性

  • 1.屬性特性的順序應該是storage、atomicity,與在Interface Builder連接UI元素時自動生成代碼一致。
  • 2.NSString應該使用copy而不是strong的屬性特性。

點語法

  • 1.點語法應該 總是 被用來訪問和修改屬性,因為它使代碼更加簡潔。

字面值

  • 1.對於NSStringNSDictionaryNSArrayNSNumber類型的值確定且不變是應當直接使用字面值,而不是調用初始化方法。

枚舉類型

  • 1.推薦使用NS_ENUM()來定義。

私有屬性

  • 1.私有屬性應該在類的實現文件中的類擴展(匿名分類)中聲明。

類構造方法

  • 1.當類構造方法被使用時,它應該返回類型是instancetype而不是id。這樣確保編譯器正確地推斷結果類型。

      @interface Airplane  
      + (instancetype)airplaneWithType:(RWTAirplaneType)type;  
      @end

CGRect函數

  • 1.當訪問CGRect里的x, y, width, 或 height時,應該使用CGGeometry函數而不是直接通過結構體來訪問。引用Apple的CGGeometry:

    在這個參考文檔中所有的函數,接受CGRect結構體作為輸入,在計算它們結果時隱式地標準化這些rectangles。因此,你的應用程序應該避免直接訪問和修改保存在CGRect數據結構中的數據。相反,使用這些函數來操縱rectangles和獲取它們的特性。

      CGRect frame = self.view.frame;  
      CGFloat x = CGRectGetMinX(frame);  
      CGFloat y = CGRectGetMinY(frame);  
      CGFloat width = CGRectGetWidth(frame);  
      CGFloat height = CGRectGetHeight(frame);  
      CGRect frame = CGRectMake(0.0, 0.0, width, height);

單例模式

```
// 單利實現
static class* _instance = nil; 
+ (instancetype)shared##class {
    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{ 
        _instance = [[super allocWithZone:NULL] init]; \
    }); 
    return _instance; 
} 
+ (id)allocWithZone:(struct _NSZone *)zone { 
    return [class shared##class]; 
} 
- (id)copyWithZone:(struct _NSZone *)zone { 
    return [class shared##class]; 
}
```

Category的命名

  • 1.Category的命名應該包含2-3個字符的前綴,用於說明Category是屬於具體的某個工程的。
分享