Objective-C 編碼規(guī)范

介紹

這份規(guī)范指南概括了使用 Objective-C 時所遵循的代碼約定。

示例代碼如下:

typedef NS_ENUM(NSInteger, StyleGuideFormat) {
    StyleGuideFormatLeft,
    StyleGuideFormatRight,
    StyleGuideFormatUp,
    StyleGuideFormatDown
};

static NSString * const StyleGuideDidChangedNotification = @"StyleGuideDidChangedNotification";
static NSString * const StyleGuideUserInfoKey = @"StyleGuideUserInfoKey";
static NSString * const StyleGuideInvalidFormatException = @"StyleGuideInvalidFormatException";

static const NSInteger StyleGuideTotalCount = 100;

@interface StyleGuide () <NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration> 

@property (nonatomic, readonly, getter = isEditable) BOOL editable;
@property (nonatomic, readwrite) NSDictionary *userInfo;

@end

@implementation StyleGuide

- (void)dealloc {
    [super dealloc];
}

- (instancetype)init {
    self = [super init];
    if (self) {
        //Custom initialization
    }
    
    return self;
}

- (NSInteger)count {
    NSInteger keyCount = 10;
    NSInteger objectCount = 20;
    NSInteger total = keyCount + objectCount;
    
    return total;
}

@end

格式

縮進

一個縮進使用 4 個空格,不要使用制表符(tab)縮進。

空行

不同的模塊之間以空行相隔,這有助于視覺清晰度和代碼組織性,有以下幾種(示例代碼參考本指南開頭的代碼):

  • @interface, @implementation, @protocol, @end, @optional, @required 與相鄰的模塊之間應該有空一行。
  • 方法體實現之間應該正好空一行。

變量

  • 指針類型變量 * 應緊靠變量名。
  • 在ARC中擁有性限定符(__strong, __weak, __unsafe_unretained, __autoreleasing)以及 __block 應位于類型名之前。
    推薦:
NSString *name;
__weak NSString *name;
__block NSString *name;

反對:

NSString* name;
NSString*name;
NSString * name;

常量

  • 定義指針類型的常量時,const 位于 * 之后,且以空格相隔。
  • 定義值類型的常量是, const 位于類型名之前。
    推薦:
NSString * const Name = @"John";
const NSInteger TotalNum = 100;

運算符

  • 單目運算符如 !, & 等與運算對象之間無空格。
  • 雙目運算符與運算對象之間以空格相隔。
  • 三目運算符 ? 應以 () 括起來。
    推薦:
    NSInteger result = (isTrue ? number1 : number2);
    
  • 如果一行出現過多的&&/||而需要分行時,&&/||置于每行行尾。
    推薦:
    if (self.count == another.count &&
        self.name == another.name &&
        self.title == another.name &&) {
    }
    

程序塊

方法的大括號和其他的大括號(if/else/switch/while 等等)始終和聲明在同一行開始,在新的一行結束。

推薦:

if (user.isHappy) {  
// Do something
} else {
// Do something else
}

方法頭

  • 在方法簽名中,在 -/+ 符號后應該有一個空格。
  • 返回值類型與方法名之間沒有空格。

推薦:

- (void)setExampleText:(NSString *)text image:(UIImage *)image;

多參數方法

當一個方法包含多個參數時,可以考慮每行一個參數,以 : 對齊。

推薦:

[NSError errorWithName:
              withType:
              withCode:];

當方法名中存在名字片段過長或者過短而無法以 : 對齊時,建議從第二行開始每行縮進四個空格,左邊對齊。

推薦:

[error short:
    keyName:
    lastLongKeyName:];

(), <>, {}, @[], @{}

  • (), <>, {} 與相鄰的模塊之間應該以空格分隔。

    推薦:

    if () {
    } else {
    }
    
    @interface NSObject <NSObject>
    

    以下幾種情況除外:

  • 方法聲明和定義。
    推薦:
```
- (instancetype)initWithImage:(UIImage *)image;
```
  • 強制類型轉換。
    推薦:
```
NSInteger count = (NSInteger)5.0f;
NSString *tip = (__bridge_transfer NSString *)CFSTR("Hello World");
```
  • enum 和 bitmask 定義。
    推薦:
```
typedef NS_ENUM(NSUInteger, NSExpressionType)
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions)
```
  • (), <>, @[], @{} 與其包含的元素之間無空格。

    推薦:

    (void)
    id<Protocol1>
    ((x = y))
    @[@1]
    array[0]
    @{@“key”: @"value"}
    dictionary[@"key"]
    

    反對:

    ( void )
    id< Protocol1 >
    ( ( x = y ) )
    @[ @1 ]
    array[ 0 ]
    @{ @“key”: @"value" }
    dictionary[ @"key" ]
    
  • (), <>, @[], @{} 其內包含的元素之間以,空格相隔。

    推薦:

    (1, 2, 3)
    id<Protocol1, Protocol2>
    @[@1, @2, @3]
    @{@"k": @"v", @"m": @"n"}
    

    反對:

    (1,2,3)
    id<Protocol1,Protocol2>
    @[@1,@2,@3]
    @{@"k": @"v",@"m": @"n"}  
    
  • @{} 中鍵值應以:空格分隔。

    推薦:

    @{@"k": @"v"}
    

    反對:

    @{@"k":@"v"}
    
  • 如果@{}中包含多個鍵值對,則建議每行一個鍵值對且左對齊。

推薦:

@{
  @"k": @"v",
  @"m": @"n"
}

反對:

@{@"k": @"v", @"m": @"n"}
  • <> 中包含多個協(xié)議,可考慮分行。從次行開始每個協(xié)議名左對齊于第一個協(xié)議名。

    推薦:

    @protocol UITableViewDelegate <NSObject, 
                                   UIScrollViewDelegate>
    

冒號“: ”兩邊以空格相連。如下:

推薦:

@interface NSString : NSObject <NSCopying, NSMutableCopying, NSSecureCoding>

屬性

按照原子性、可讀性、擁有性、以及自定義 Accesor Method 方法順序排列,默認值可省去。

推薦:

@property (nonatomic, readonly, copy, setter = setTitle) NSString *title;

命名

采用 camel-casing 命名法:將每個單詞的首字母大寫然后拼接起來。

基本原則

  • 清晰

    • 命名的清晰性高于簡單和簡潔,應在追求清晰的同時盡量保持簡潔。
    • 盡量避免使用縮寫,即使很長也要拼寫出來。
    • 一些通用的縮寫可以看情況使用。
    • 避免二義性。
  • 一致性
    盡量保持命名在整個程序內的一致性,主要包含兩點:

    • 同一名字表示相同的意義。
    • 同樣的概念使用同一名字表示,切忌同時使用多個名字。

前綴

  • 應用程序代碼不建議使用前綴,當開發(fā)第三方使用的庫時則應該使用前綴。
  • 協(xié)議名、函數名、常量名、以及枚舉名應以其所關聯的類名作為前綴。

推薦:

typedef NS_ENUM(NSInteger, UIScreenOverscanCompensation);

NSString *const UIScreenDidConnectNotification;

NSData *UIImagePNGRepresentation(UIImage *image);

@protocol UIAlertViewDelegate <NSObject>

類名通常應該由名詞組成,并完全遵循 camel-casing

協(xié)議

協(xié)議名字通常根據其包含的方法而定,有以下幾類:

  • 包含了一些相關的方法,通常作為一組類的接口,其命名方式為:操作 + ing。

    推薦:

    @protocol NSLocking
    
    - (void)lock;
    - (void)unlock;
    
    @end
             
    @protocol NSCopying
    
    - (id)copyWithZone:(NSZone *)zone;
    
    @end
    
  • 包含一組相關的方法,用于在代理對象和被代理對象之間傳遞數據,其命名方式為:類名 + DataSource。

    推薦:

    @protocol UITableViewDataSource <NSObject>
    
  • 包含一組相關的方法, 用于響應操作和控制程序流,其命名方式為:類名 + Delegate。

    推薦:

    @protocol UIAlertViewDelegate <NSObject>
    
    @optional
    
    - (void)alertView: clickedButtonAtIndex: ;
    - (BOOL)alertViewShouldEnableFirstOtherButton:;
    

變量

  • 變量名字采用 camel-casing 命名法且首字母小寫。

  • 對于數組類型的變量,其命名方式為:名詞/詞組 + s。

    推薦:

    NSMutableArray *gestureRecognizers;
    

    反對:

    NSMutableArray *gestureRecognizerArray;
    
  • 對于字典類型的變量,其命名方式為:名詞/詞組 + s/Info。

    推薦:

    NSDictionary *fileAttributes;
    NSDictionary *userInfo;
    

    反對:

    NSDictionary *fileAttributeDictionary;
    NSDictionary *userDictionary;
    
  • 實例變量遵從變量的命名方式,且以 _ 為前綴。

屬性

屬性遵從變量的命名方式,如果屬性名字是形容詞,需指定 get 訪問器。

推薦:

@property (nonatomic, readonly, getter = isPlayable) BOOL playable;

枚舉

枚舉常量的名字應以枚舉變量的名字為前綴,其命名方式為:枚舉類型 + 枚舉狀態(tài)。枚舉類型的命名方式為:類名 + 名詞/詞組。

推薦:

typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};

通知

通知命名方式:[Name of associated class] + [Did | Will] + [UniquePartOfName] + Notification

推薦:

NSSystemTimeZoneDidChangeNotification
MPMediaLibraryDidChangeNotification
UIKeyboardWillShowNotification

異常

異常命名方式:[Name of associated class] + [UniquePartOfName] + Exception。

推薦:

NSRangeException
NSInvalidArgumentException

鍵指鍵值對中的鍵,用在字典中。其命名方式:[Name of associated class] + [UniquePartOfName] + Key。

推薦:

UIKeyboardFrameBeginUserInfoKey
ALAssetLibraryUpdatedAssetsKey

方法

  • 方法命名遵從變量的命名方式。

  • 對于表示動作的方法,以動詞開頭。

    不要使用 do 或者 does 等很少有意義的語氣助詞。另外,不要在動詞前使用副詞或形容詞。

  • 對于返回對象屬性的方法,建議直接使用屬性作為方法名。

    反對使用 get 作為前綴,或者其他的動詞,即使該方法并非直接返回結果,而是需要一些運算。存在多個返回值時,則

    推薦:

      - (NSSize)cellSize; 
    

    反對:

      - (NSSize)calcCellSize;
      - (NSSize)getCellSize;
    
  • 在所有參數前都要有關鍵字。

    推薦:

    - (void)sendAction:(SEL)aSelector toObject:(id)anObject;  
    

    反對:

    - (void)sendAction:(SEL)aSelector :(id)anObject;
    
  • 描述參數的詞緊靠在參數之前。

    推薦:

    - (id)viewWithTag:(NSInteger)aTag;
    

    反對:

    - (id)taggedView:(int) aTag;
    
  • 針對 Cocoa 或 第三方庫中的類所添加的類別中的方法,需為方法名添加前綴,基于項目名的縮寫構成前綴,形如TT_。對于派生自他們的子類,如果擔心添加的方法可能會跟基類中得方法名沖突,通用建議使用前綴方法名。

    考慮這樣一個場景,在8.0發(fā)布之前你為 NSString 添加了一個類別,其中包含一個這樣的方法 *containsString:(NSString )aString 。待到8.0發(fā)布時,蘋果不幸地意識到確實需要一個這樣的方法,在 NSString 中新增了同樣的一個接口,這絕對是災難性地!

其他

注釋

當需要的時候,注釋應該被用來解釋 為什么 特定代碼做了某些事情。所使用的任何注釋必須保持最新否則就刪除掉。

通常應該避免一大塊注釋,代碼就應該盡量作為自身的文檔。

Warning的要求

除了第三方庫,不能引入新的警告

類前置聲明

通常引用一個類有兩種辦法:一種是通過#import方式引入;另一種是通過@class引入;在頭文件中通過@class引入這個類作為一個類型使用,減少頭文件的重復包,提升編譯效率。在實現文件中,如果需要引用到被引用類的實體變量或者方法時,還需要使用#import方式引入被引用類。

頭文件聲明:

@class StyleGuide;

StyleGuide *styleGuide;

實現文件引入頭文件:

#import @"StyleGuilde.h"

#pragma mark分組

一些類(尤其是一些控制器類)可能很長,方法和函數彈出菜單可以便于代碼導航。此時加入#pragma 指令對代碼進行邏輯組織很有效果。提高可讀性

例如:

#pragma mark - Initialization

私有方法屬性定義

外部不需使用的屬性方法成員變量不能暴露到.h文件。在類的.m文件中,采用類別來實現私有方法

例如:.m文件中定義

@interface MyClass()
  - (void)privateMethod;
@end

小貼士

點“.”語法

應該 始終 使用點“.”語法來訪問或者修改屬性,除此之外,不得使用點“.”語法。

推薦:

view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate;

反對:

[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate;

條件判斷

條件判斷主體部分應該始終使用大括號括住來防止[出錯][Condiationals_1],即使它可以不用大括號(例如它只需要一行)。這些錯誤包括添加第二行(代碼)并希望它是 if 語句的一部分時。還有另外一種[更危險的][Condiationals_2],當 if 語句里面的一行被注釋掉,下一行就會在不經意間成為了這個 if 語句的一部分。此外,這種風格也更符合所有其他的條件判斷,因此也更容易檢查。

推薦:

if (isFinished) {
    return success;
}

反對:

if (isFinished)
    return success;

if (isFinished) return success;

三目運算符

三目運算符,? ,只有當它可以增加代碼清晰度或整潔時才使用。單一的條件都應該優(yōu)先考慮使用,多條件時通常使用 if 語句會更易懂。

推薦:

result = (a > b ? x : y);

反對:

result = (a > b ? x = c > d ? c : d : y);

錯誤處理

當引用一個返回錯誤參數(error parameter)的方法時,應該針對返回值,而非錯誤變量。一些蘋果的 API 在成功的情況下會寫一些垃圾值給錯誤參數(如果非空),所以針對錯誤變量可能會造成虛假結果。

推薦:

NSError *error;
if (![self trySomethingWithError:&error]) {
    // 處理錯誤
}

反對:

NSError *error;
[self trySomethingWithError:&error];
if (error != nil) {
    // 處理錯誤
}

init 和 dealloc

dealloc 方法應該放在@implementation的最上面,并且剛好在 @synthesize@dynamic 語句的后面。在任何類中,init 都應該直接放在 dealloc 方法的下面。

init 方法的結構應該像這樣:

- (instancetype)init {
    self = [super init]; 
    if (self != nil) {
        // Custom initialization
    }

    return self;
}

字面量

每當創(chuàng)建 NSStringNSDictionaryNSArray,和 NSNumber 類的不可變實例時,應使用 @"", @{}, @[], @()方式生成實例。要注意 nil 值不能傳給 NSArrayNSDictionary 字面量,這樣做會導致崩潰。

推薦:

NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingZIPCode = @10018;

反對:

NSArray *names = [NSArray arrayWithObjects:@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul", nil];
NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys: @"Kate", @"iPhone", @"Kamal", @"iPad", @"Bill", @"Mobile Web", nil];
NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES];
NSNumber *buildingZIPCode = [NSNumber numberWithInteger:10018];

CGRect 函數

當訪問一個 CGRectxywidthheight 時,應該使用[CGGeometry 函數][CGRect-Functions_1]代替直接訪問結構體成員。蘋果的 CGGeometry 參考中說到:

All functions described in this reference that take CGRect data structures as inputs implicitly standardize those rectangles before calculating their results. For this reason, your applications should avoid directly reading and writing the data stored in the CGRect data structure. Instead, use the functions described here to manipulate rectangles and to retrieve their characteristics.

推薦:

CGRect frame = self.view.frame;

CGFloat x = CGRectGetMinX(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);

反對:

CGRect frame = self.view.frame;

CGFloat x = frame.origin.x;
CGFloat y = frame.origin.y;
CGFloat width = frame.size.width;
CGFloat height = frame.size.height;

CGRect-Functions_1

應該盡量避免使用宏定義,除非沒有別的選擇,通常應當使用常量和枚舉,它們具有更強的類型檢查,而更加安全。

推薦:

static const CGFloat ThumbnailHeight = 50.0f;

反對:

#define ThumbnailHeight 2.0f

枚舉類型

當使用 enum 時,建議使用新的基礎類型規(guī)范,因為它具有更強的類型檢查和代碼補全功能。現在 SDK 包含了一個宏來鼓勵使用使用新的基礎類型 - NS_ENUM()

推薦:

typedef NS_ENUM(NSInteger, UIImageResizingMode) {
    UIImageResizingModeTile,
    UIImageResizingModeStretch,
};

位掩碼

當用到位掩碼時,使用 NS_OPTIONS 宏。

舉例:

typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
    UIViewAutoresizingNone                 = 0,
    UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,
    UIViewAutoresizingFlexibleWidth        = 1 << 1,
    UIViewAutoresizingFlexibleRightMargin  = 1 << 2,
    UIViewAutoresizingFlexibleTopMargin    = 1 << 3,
    UIViewAutoresizingFlexibleHeight       = 1 << 4,
    UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};

布爾

typedef signed char BOOL;
#define YES ((BOOL)1)
#define NO  ((BOOL)0)

以上代碼片斷來自objc.h。永遠不將布爾變量直接和 YES 進行比較,因為 YES 被定義為 1,而 BOOL 可以多達 8 位。

if (isAwesome)
if (![someObject boolValue])

反對:

if ([someObject boolValue] == NO)
if (isAwesome == YES) 

單例

單例對象應該使用線程安全的模式創(chuàng)建共享的實例。

+ (instancetype)sharedInstance {
   static id sharedInstance = nil;

   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
      sharedInstance = [[self alloc] init];
   });

   return sharedInstance;
}

通用的縮寫

Abbreviation Meaning and comments
alloc Allocate.
alt Alternate.
app Application.
calc Calculate.
dealloc Deallocate.
func Function.
horiz Horizontal.
info Information.
init Initialize.
int Integer.
max Maximum.
min Minimum.
msg Message.
nib Interface Builder archive.
pboard Pasteboard (but only in constants).
rect Rectangle.
Rep Representation.
temp Temporary.
vert Vertical.

以下是一些在計算機領域比較知名的縮寫:

ASCII

PDF

XML

HTML

URL

RTF

HTTP

TIFF

JPG

PNG

GIF

LZW

ROM

RGB

CMYK

MIDI

FTP

參考文章

Cocoa 編碼指南

Google Objective-C Style Guide

NYTimes Objective-C Style Guide

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,853評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,815評論 2 372

推薦閱讀更多精彩內容