Objective-C 編碼風格指南

本文轉自:Objective-C 編碼風格指南 | www.samirchen.comhtml

背景

保證本身的代碼遵循團隊統一的編碼規範是一個碼農的基本節操,可以進入一個有統一編碼規範的團隊則是一個碼農的福氣。ios

本文主要是對如下幾個編碼規範的整理:git

這裏有些關於編碼風格 Apple 官方文檔,若是有些東西沒有說起,能夠在如下文檔來查找更多細節:github

語言

使用美式英語。別用拼音。objective-c

推薦:xcode

UIColor *myColor = [UIColor whiteColor];

不推薦:安全

UIColor *myColour = [UIColor whiteColor];
UIColor *woDeYanSe = [UIColor whiteColor];

代碼結構

使用 #pragma mark - 根據「代碼功能類別」、「protocol/delegate 方法實現」等依據對代碼進行分塊組織。代碼的組織順序從總體上儘可能遵循咱們的認知順序,組織規範以下:app

// 先描述這個類是什麼,它的屬性有什麼。
// 每一個屬性的 getter 方法在前,setter 方法在後。屬性的 getter/setter 方法的順序與屬性聲明順序一致。
#pragma mark - Property

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

// 再描述這個類的生命週期,從出生到消亡。
// 按照生命週期的順序來排序相關方法。
#pragma mark - Lifecycle

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

// 若是這是一個 UIViewController 類,能夠接着描述這個頁面能夠跳轉到的其餘頁面。
#pragma mark - Navigation

- (void)goToMainPage {}
- (void)goToUserPage {}


// 接着描述這個類的響應方法,能作哪些交互。
// 好比:按鈕點擊的響應方法、手勢的響應方法等等。
#pragma mark - Action

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

// 而後描述這個類的其餘分組方法。這裏的分組能夠是多個,如何分組能夠由你擴展。
#pragma mark - <Other Functional Grouping>

- (void)someGroupedMethod {}


// 接下來描述這個類實現的 Protocol/Delegate 的方法。
// 先放自定義的 Protocol/Delegate 方法,後放官方提供的 Protocal/Delegate 方法。
#pragma mark - <Protocol/Delegate Conformance>
#pragma mark - UITextFieldDelegate
#pragma mark - UITableViewDataSource
#pragma mark - UITableViewDelegate

// 而後是對繼承的父類中方法重載。
// 先發自定義的父類方法重載,後方官方父類的方法重載。
#pragma mark - <Superclass Overridden>

- (void)someOverriddenMethod {}

#pragma mark - NSObject

- (NSString *)description {}

代碼如流水同樣,去敘述一個類。iphone

空格

  • 使用 Tab(4 個空格) 來作代碼縮進,不要用空格。
  • 方法大括號和其餘大括號(if/else/switch/while 等)老是在同一行打開,但在新的一行關閉。

推薦:ide

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

不推薦:

if (user.isHappy)
{
    // Do something
}
else {
    // Do something else
}
  • 在兩個方法之間應該間隔一行且只有一行,這樣在視覺上更清晰。在方法內能夠爲分隔不一樣的功能代碼而空行,但一般都會把具備特定功能的代碼抽出來成爲一個新方法。
  • 優先使用 auto-synthesis。若是有必要 @synthesize@dynamic 的聲明應該在實現代碼中各佔一行。
  • 在調用方法時,避免以冒號對齊的格式來排版。由於有時前綴較長或者包含 Block 時會使得這種方式排版的代碼易讀性不好。

推薦:

// blocks are easily readable
[UIView animateWithDuration:1.0 animations:^{
    // something
} completion:^(BOOL finished) {
    // something
}];

不推薦:

// colon-aligning makes the block indentation hard to read
[UIView animateWithDuration:1.0
                 animations:^{
                    // something
                 }
                 completion:^(BOOL finished) {
                    // something
                 }];

註釋

當你寫代碼註釋時,須要注意你的註釋是解釋爲何要有這段代碼。一段註釋要確保跟代碼一致更新,不然就刪掉。

通常避免使用塊註釋,這樣佔用空間太大,代碼應該儘可能作到自解釋,代碼即註釋。固然,也有例外:你的註釋是爲了生成文檔用。

命名

你多是從 Java、Python、C++ 或是其餘語言轉過來的,可是來到 Objective-C 這地盤,請遵照蘋果的命名規範,這樣你才能使得本身的代碼與周邊和諧統一,尤爲須要注意 memory management rules (NARC) 相關的命名規範.

長的、描述性的方法和變量命名是好的,這使得代碼更容易被讀懂。

推薦:

UIButton *settingsButton;

不推薦:

UIButton *setBut;

在類名和常量名上應該使用兩個或三個字母的前綴(好比:CX、TB 等等)。可是在 Core Data 實體命名時應該省略前綴。

常量應該使用駝峯式命名規則,全部的單詞首字母大寫,並加上與類名有關的前綴。

推薦:

static NSTimeInterval const RWTTutorialViewControllerNavigationFadeAnimationDuration = 0.3;

不推薦:

static NSTimeInterval const fadetime = 1.7;

屬性也是使用駝峯式命名規則,但首單詞的首字母小寫。對屬性使用 auto-synthesis,而不是手動編寫 @synthesize 語句,除非你有一個好的理由。

推薦:

@property (strong, nonatomic) NSString *descriptiveVariableName;

不推薦:

id varnm;

下劃線

當使用屬性時,用 self. 來訪問,這就意味着全部的屬性都頗有辨識度,由於他們前面有 self.

可是有 2 個特列:

  • 在屬性的 getter/setter 方法中必須使用下劃線,(好比:_variableName)。
  • 在類的初始化和銷燬方法中,有時爲了不屬性的 getter/setter 方法的反作用,可使用下劃線。

局部變量不要包含下劃線。

方法

在方法簽名中,應該在方法類型(-/+ 符號)以後有一個空格。在方法各段之間應該也有一個空格(符合 Apple 的風格)。在參數以前應該包含一個描述性的關鍵字來描述參數。

and 這個詞的用法應該保留,它不該該用於多個參數之間。

推薦:

- (void)setExampleText:(NSString *)text image:(UIImage *)image;
- (void)sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag;
- (id)viewWithTag:(NSInteger)tag;
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;

不推薦:

-(void)setT:(NSString *)text i:(UIImage *)image;
- (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag;
- (id)taggedView:(NSInteger)tag;
- (instancetype)initWithWidth:(CGFloat)width andHeight:(CGFloat)height;
- (instancetype)initWith:(int)width and:(int)height;  // Never do this.

變量

變量儘可能以描述性的方式來命名。除了在 for() 循環中,應該儘可能避免單個字符的變量命名。

表示指針的星號應該和變量名在一塊兒,好比:應該是 NSString *text,而不是 NSString* text 或者 NSString * text,除了一些特別的狀況。

應該使用私有屬性,而不要再使用實例變量了。這樣能夠保持代碼的一致性。

除了在一些初始化方法(init, initWithCoder:, etc…)、銷燬方法(dealloc)和自定義的 setters/getters 方法中外,不要直接使用下劃線的方式訪問實例變量。詳情參見這裏

推薦:

@interface RWTTutorial : NSObject

@property (strong, nonatomic) NSString *tutorialName;

@end

不推薦:

@interface RWTTutorial : NSObject {
    NSString *tutorialName;
}

屬性特性

屬性特性的順序應該是:存儲特性、訪問特性、原子特性、getter/setter。其中存儲特性、原子特性應該顯式地列出來,有助於新手閱讀代碼。與在 Interface Builder 鏈接 UI 元素時自動生成代碼一致。

推薦:

@property (weak, nonatomic) IBOutlet UIView *containerView;
@property (strong, nonatomic) NSString *tutorialName;
@property (assign, readonly, nonatomic, getter=isFinished) BOOL finished;

不推薦:

@property (nonatomic, weak) IBOutlet UIView *containerView;
@property (nonatomic) NSString *tutorialName;

具備值拷貝類型特定的屬性(如:NSString)應該優先使用 copy 而不是 strong。這是由於即便你聲明一個 NSString 類型的屬性,有人也可能傳入一個 NSMutableString 的實例,而後在你沒有注意的狀況下修改它。

推薦:

@property (copy, nonatomic) NSString *tutorialName;

不推薦:

@property (strong, nonatomic) NSString *tutorialName;

點符號語法

點符號語法是對方法調用語法很方便的一種封裝。在返回屬性時,使用點符號語法,屬性的 getter/setter 方法也能確保被調用。更多信息閱讀這裏

咱們應該老是使用點符號語法來訪問或者修改屬性,由於它使得代碼更加簡潔。[] 則應該用在其餘場景下。

推薦:

NSInteger arrayCount = self.array.count; // `count` is a property of NSArray.
view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate; // `sharedApplication` is not a property of UIApplication.

不推薦:

NSInteger arrayCount = [self.array count]; // `count` is a property of NSArray.
[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate; // `sharedApplication` is not a property of UIApplication.

字面值

在建立 NSStringNSDictionaryNSArrayNSNumber 對象時,應該使用字面值語法。尤爲須要注意建立 NSArrayNSDictionary 對象時,不能傳入 nil,不然會形成 crash。

推薦:

NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
NSDictionary *productManagers = @{@"iPhone": @"Kate", @"iPad": @"Kamal", @"Mobile Web": @"Bill"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingStreetNumber = @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 *buildingStreetNumber = [NSNumber numberWithInteger:10018];

常量

比起硬編碼字符串或數字的形式,咱們應該常量來定義複用型變量,由於常量更容易被修改,而不須要咱們 find + replace。使用常量時,咱們應該使用 static 而不是 #define 一個類型不明的宏。

推薦:

static NSString * const RWTAboutViewControllerCompanyName = @"RayWenderlich.com";

static CGFloat const RWTImageThumbnailHeight = 50.0;

不推薦:

#define CompanyName @"RayWenderlich.com"

#define thumbnailHeight 2

枚舉類型

當使用枚舉時,咱們要用 NS_ENUM() 而不是 enum

例如:

typedef NS_ENUM(NSInteger, RWTLeftMenuTopItemType) {
    RWTLeftMenuTopItemMain,
    RWTLeftMenuTopItemShows,
    RWTLeftMenuTopItemSchedule
};

你能夠顯示的賦值:

typedef NS_ENUM(NSInteger, RWTGlobalConstants) {
    RWTPinSizeMin = 1,
    RWTPinSizeMax = 5,
    RWTPinCountMin = 100,
    RWTPinCountMax = 500,
};

不推薦:

enum GlobalConstants {
    kMaxPinSize = 5,
    kMaxPinCount = 500,
};

Case 語句

除非編譯器強制要求,通常在 Case 語句中是不須要加括號的。當一個 Case 語句包含多行代碼,應該加上括號。

switch (condition) {
    case 1:
        // ...
        break;
    case 2: {
        // ...
        // Multi-line example using braces
        break;
    }
    case 3:
        // ...
        break;
    default: 
        // ...
        break;
}

若是一段代碼被多個 Case 語句共享執行,那就要用 fall-through,即在 Case 語句中刪除 break 語句,讓代碼可以執行到下一個 Case 中去,爲了代碼清晰明瞭,用了 fall-through 時須要註釋一下。

switch (condition) {
    case 1:
        // ** fall-through! **
    case 2:
        // code executed for values 1 and 2
    break;
    default: 
        // ...
        break;
}

在 Swith 中使用枚舉類型時,是不須要 default 語句的,例如:

RWTLeftMenuTopItemType menuType = RWTLeftMenuTopItemMain;

switch (menuType) {
    case RWTLeftMenuTopItemMain:
        // ...
        break;
    case RWTLeftMenuTopItemShows:
        // ...
        break;
    case RWTLeftMenuTopItemSchedule:
        // ...
        break;
}

私有屬性

私有屬性應該在類的實現文件(xxx.m)中的匿名擴展(Anonymous Category)中聲明。除非是要去擴展一個類,不然不要使用命名擴展(Named Category)。若是你要測試私有屬性,你能夠經過 <headerfile>+Private.h 的方式把私有屬性暴露給測試人員。

For Example:

@interface RWTDetailViewController ()

@property (strong, nonatomic) GADBannerView *googleAdView;
@property (strong, nonatomic) ADBannerView *iAdView;
@property (strong, nonatomic) UIWebView *adXWebView;

@end

布爾值

Objective-C 使用 YESNO 做爲 BOOL 值。所以,truefalse 只應該在 CoreFoundation、C、C++ 代碼中使用。因爲 nil 會被解析爲 NO,因此沒有必要在條件語句中去比較它。 另外,永遠不要拿一個對象和 YES 比較,由於 YES 被定義爲 1 而且 BOOL 值最多 8 bit。

這時爲了在不一樣代碼中保持一致性和簡潔性。

推薦:

if (someObject) {}
if (![anotherObject boolValue]) {}

不推薦:

if (someObject == nil) {}
if ([anotherObject boolValue] == NO) {}
if (isAwesome == YES) {} // Never do this.
if (isAwesome == true) {} // Never do this.

若是一個 BOOL 類型的屬性是形容詞,那麼它的命名能夠省略掉 「is」 前綴,可是咱們仍是須要給它指定慣用的 getter 方法名,例如:

@property (assign, getter=isEditable) BOOL editable;

更多內容詳見:Cocoa Naming Guidelines

條件語句

條件語句應該使用大括號包圍,即便可以不用時(好比條件代碼只有一行)也不要省略大括號,這樣能夠最大可能的避免出錯(好比條件語句不當心被註釋了),同時也保持了大括號的使用風格一致。

推薦:

if (!error) {
    return success;
}

不推薦:

if (!error)
    return success;

或者

if (!error) return success;

三元操做符

只有在能提升代碼清晰性和可讀性的狀況下,才應該使用三元操做符 ?:。單個條件判斷時能夠用到它,多個條件判斷時仍是用 if 來提升代碼可讀性吧。通常來講,使用三元操做符最好的場景是根據條件來賦值的時候。

非布爾類型的變量與某對象比較時最好加上括號來提升代碼可讀性,若是被比較的變量是布爾類型那就不用括號了。

推薦:

NSInteger value = 5;
result = (value != 0) ? x : y;

BOOL isHorizontal = YES;
result = isHorizontal ? x : y;

不推薦:

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

初始化方法

Init 方法應該遵循 Apple 生成代碼模板的命名規則。返回類型應該使用 instancetype 而不是 id

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

類構造方法

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

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

查看更多關於 instancetype 的信息:NSHipster.com

CGRect 方法

當訪問 CGRect 的 xywidthheight 屬性時,老是使用 CGGeometry functions 相關的函數,而不是直接從結構體訪問。

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 = CGRectMake(0.0, 0.0, width, height);

不推薦:

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 frame = (CGRect){ .origin = CGPointZero, .size = frame.size };

黃金路徑

當使用條件語句編寫邏輯時,左手的代碼應該是 "golden" 或 "happy" 路徑。也就是說,不要嵌套多個 if 語句,即便寫多個 return 語句也是 OK 的。

推薦:

- (void)someMethod {
    if (![someOther boolValue]) {
        return;
    }

    //Do something important
}

不推薦:

- (void)someMethod {
    if ([someOther boolValue]) {
        //Do something important
    }
}

單例

單例對象應該使用線程安全的方式來建立共享實例。

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

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

    return sharedInstance;
}

這樣會防止 possible and sometimes prolific crashes

換行符

換行符主要是在提升打印和網上閱讀時的代碼可讀性時顯得很重要。

例如:

self.productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];

一行較長的代碼最好能換行再加一個 Tab。

self.productsRequest = [[SKProductsRequest alloc] 
  initWithProductIdentifiers:productIdentifiers];

Xcode 工程

物理文件應該與 Xcode 項目目錄保持同步來避免文件管理雜亂。建立任何 Xcode group 應該與文件系統中的文件夾保持映射。代碼分類除了以類型分類,從大的方面上也應該以功能分類。

若是能夠的話,打開 Xcode 的 Treat Warnings as Errors 來下降對 warning 的容忍度。若是有時候確實要忽略某一個 warning,你可使用 Clang's pragma feature

相關文章
相關標籤/搜索