Objective-C 編碼風(fēng)格指南

本文轉(zhuǎn)自:Objective-C 編碼風(fēng)格指南 | www.samirchen.com

背景

保證自己的代碼遵循團(tuán)隊統(tǒng)一的編碼規(guī)范是一個碼農(nóng)的基本節(jié)操,能夠進(jìn)入一個有統(tǒng)一編碼規(guī)范的團(tuán)隊則是一個碼農(nóng)的福氣。

本文主要是對以下幾個編碼規(guī)范的整理:

這里有些關(guān)于編碼風(fēng)格 Apple 官方文檔,如果有些東西沒有提及,可以在以下文檔來查找更多細(xì)節(jié):

語言

使用美式英語。別用拼音。

推薦:

UIColor *myColor = [UIColor whiteColor];

不推薦:

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

代碼結(jié)構(gòu)

使用 #pragma mark - 根據(jù)「代碼功能類別」、「protocol/delegate 方法實(shí)現(xiàn)」等依據(jù)對代碼進(jìn)行分塊組織。代碼的組織順序從整體上盡量遵循我們的認(rèn)知順序,組織規(guī)范如下:

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

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

// 再描述這個類的生命周期,從出生到消亡。
// 按照生命周期的順序來排序相關(guān)方法。
#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 類,可以接著描述這個頁面可以跳轉(zhuǎn)到的其他頁面。
#pragma mark - Navigation

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


// 接著描述這個類的響應(yīng)方法,能做哪些交互。
// 比如:按鈕點(diǎn)擊的響應(yīng)方法、手勢的響應(yīng)方法等等。
#pragma mark - Action

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

// 然后描述這個類的其他分組方法。這里的分組可以是多個,如何分組可以由你擴(kuò)展。
#pragma mark - <Other Functional Grouping>

- (void)someGroupedMethod {}


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

// 然后是對繼承的父類中方法重載。
// 先發(fā)自定義的父類方法重載,后方官方父類的方法重載。
#pragma mark - <Superclass Overridden>

- (void)someOverriddenMethod {}

#pragma mark - NSObject

- (NSString *)description {}

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

空格

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

推薦:

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

不推薦:

if (user.isHappy)
{
    // Do something
}
else {
    // Do something else
}
  • 在兩個方法之間應(yīng)該間隔一行且只有一行,這樣在視覺上更清晰。在方法內(nèi)可以為分隔不同的功能代碼而空行,但通常都會把具有特定功能的代碼抽出來成為一個新方法。
  • 優(yōu)先使用 auto-synthesis。如果有必要 @synthesize@dynamic 的聲明應(yīng)該在實(shí)現(xiàn)代碼中各占一行。
  • 在調(diào)用方法時,避免以冒號對齊的格式來排版。因?yàn)橛袝r前綴較長或者包含 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
                 }];

注釋

當(dāng)你寫代碼注釋時,需要注意你的注釋是解釋為什么要有這段代碼。一段注釋要確保跟代碼一致更新,否則就刪掉。

一般避免使用塊注釋,這樣占用空間太大,代碼應(yīng)該盡量做到自解釋,代碼即注釋。當(dāng)然,也有例外:你的注釋是為了生成文檔用。

命名

你可能是從 Java、Python、C++ 或是其他語言轉(zhuǎn)過來的,但是來到 Objective-C 這地盤,請遵守蘋果的命名規(guī)范,這樣你才能使得自己的代碼與周邊和諧統(tǒng)一,尤其需要注意 memory management rules (NARC) 相關(guān)的命名規(guī)范.

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

推薦:

UIButton *settingsButton;

不推薦:

UIButton *setBut;

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

常量應(yīng)該使用駝峰式命名規(guī)則,所有的單詞首字母大寫,并加上與類名有關(guān)的前綴。

推薦:

static NSTimeInterval const RWTTutorialViewControllerNavigationFadeAnimationDuration = 0.3;

不推薦:

static NSTimeInterval const fadetime = 1.7;

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

推薦:

@property (strong, nonatomic) NSString *descriptiveVariableName;

不推薦:

id varnm;

下劃線

當(dāng)使用屬性時,用 self. 來訪問,這就意味著所有的屬性都很有辨識度,因?yàn)樗麄兦懊嬗?self.。

但是有 2 個特列:

  • 在屬性的 getter/setter 方法中必須使用下劃線,(比如:_variableName)。
  • 在類的初始化和銷毀方法中,有時為了避免屬性的 getter/setter 方法的副作用,可以使用下劃線。

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

方法

在方法簽名中,應(yīng)該在方法類型(-/+ 符號)之后有一個空格。在方法各段之間應(yīng)該也有一個空格(符合 Apple 的風(fēng)格)。在參數(shù)之前應(yīng)該包含一個描述性的關(guān)鍵字來描述參數(shù)。

and 這個詞的用法應(yīng)該保留,它不應(yīng)該用于多個參數(shù)之間。

推薦:

- (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() 循環(huán)中,應(yīng)該盡量避免單個字符的變量命名。

表示指針的星號應(yīng)該和變量名在一起,比如:應(yīng)該是 NSString *text,而不是 NSString* text 或者 NSString * text,除了一些特別的情況。

應(yīng)該使用私有屬性,而不要再使用實(shí)例變量了。這樣可以保持代碼的一致性。

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

推薦:

@interface RWTTutorial : NSObject

@property (strong, nonatomic) NSString *tutorialName;

@end

不推薦:

@interface RWTTutorial : NSObject {
    NSString *tutorialName;
}

屬性特性

屬性特性的順序應(yīng)該是:存儲特性、訪問特性、原子特性、getter/setter。其中存儲特性、原子特性應(yīng)該顯式地列出來,有助于新手閱讀代碼。與在 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)應(yīng)該優(yōu)先使用 copy 而不是 strong。這是因?yàn)榧词鼓懵暶饕粋€ NSString 類型的屬性,有人也可能傳入一個 NSMutableString 的實(shí)例,然后在你沒有注意的情況下修改它。

推薦:

@property (copy, nonatomic) NSString *tutorialName;

不推薦:

@property (strong, nonatomic) NSString *tutorialName;

點(diǎn)符號語法

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

我們應(yīng)該總是使用點(diǎn)符號語法來訪問或者修改屬性,因?yàn)樗沟么a更加簡潔。[] 則應(yīng)該用在其他場景下。

推薦:

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.

字面值

在創(chuàng)建 NSStringNSDictionary、NSArrayNSNumber 對象時,應(yīng)該使用字面值語法。尤其需要注意創(chuàng)建 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];

常量

比起硬編碼字符串或數(shù)字的形式,我們應(yīng)該常量來定義復(fù)用型變量,因?yàn)槌A扛菀妆恍薷模恍枰覀?find + replace。使用常量時,我們應(yīng)該使用 static 而不是 #define 一個類型不明的宏。

推薦:

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

static CGFloat const RWTImageThumbnailHeight = 50.0;

不推薦:

#define CompanyName @"RayWenderlich.com"

#define thumbnailHeight 2

枚舉類型

當(dāng)使用枚舉時,我們要用 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 語句

除非編譯器強(qiáng)制要求,一般在 Case 語句中是不需要加括號的。當(dāng)一個 Case 語句包含多行代碼,應(yīng)該加上括號。

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

如果一段代碼被多個 Case 語句共享執(zhí)行,那就要用 fall-through,即在 Case 語句中刪除 break 語句,讓代碼能夠執(zhí)行到下一個 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;
}

私有屬性

私有屬性應(yīng)該在類的實(shí)現(xiàn)文件(xxx.m)中的匿名擴(kuò)展(Anonymous Category)中聲明。除非是要去擴(kuò)展一個類,否則不要使用命名擴(kuò)展(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 只應(yīng)該在 CoreFoundation、C、C++ 代碼中使用。由于 nil 會被解析為 NO,所以沒有必要在條件語句中去比較它。 另外,永遠(yuǎn)不要拿一個對象和 YES 比較,因?yàn)?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;

更多內(nèi)容詳見:Cocoa Naming Guidelines

條件語句

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

推薦:

if (!error) {
    return success;
}

不推薦:

if (!error)
    return success;

或者

if (!error) return success;

三元操作符

只有在能提高代碼清晰性和可讀性的情況下,才應(yīng)該使用三元操作符 ?:。單個條件判斷時可以用到它,多個條件判斷時還是用 if 來提高代碼可讀性吧。一般來說,使用三元操作符最好的場景是根據(jù)條件來賦值的時候。

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

推薦:

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 方法應(yīng)該遵循 Apple 生成代碼模板的命名規(guī)則。返回類型應(yīng)該使用 instancetype 而不是 id。

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

類構(gòu)造方法

當(dāng)使用類構(gòu)造方法時,應(yīng)該返回的類型是 instancetype 而不是 id。這樣確保編譯器正確地推斷結(jié)果類型。

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

查看更多關(guān)于 instancetype 的信息:NSHipster.com。

CGRect 方法

當(dāng)訪問 CGRect 的 x、y、widthheight 屬性時,總是使用 CGGeometry functions 相關(guān)的函數(shù),而不是直接從結(jié)構(gòu)體訪問。

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

黃金路徑

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

推薦:

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

    //Do something important
}

不推薦:

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

單例

單例對象應(yīng)該使用線程安全的方式來創(chuàng)建共享實(shí)例。

+ (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。

換行符

換行符主要是在提高打印和網(wǎng)上閱讀時的代碼可讀性時顯得很重要。

例如:

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

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

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

Xcode 工程

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

如果可以的話,打開 Xcode 的 Treat Warnings as Errors 來降低對 warning 的容忍度。如果有時候確實(shí)要忽略某一個 warning,你可以使用 Clang's pragma feature。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容