Objective-C代碼規範

Objective-C代碼規範

前言

Apple公司提供了一些代碼規範文檔。若是有內容未在此文檔中說起,請參考以下內容:html

適用範圍

全部適用Objective-C語言開發的項目。 在這裏咱們但願以相似斷言的方式,你們逐條對比寫出的代碼和下列規範是否吻合,以達到預期的代碼的可讀性。ios

代碼規範

命名

基於iOS objective-c項目對於命名,目前分爲變量名和函數名兩類git

變量名

在這裏咱們把描述一個事物或者抽象事物的描述符統稱爲變量名。變量名目前分爲幾類: 類名,協議名,組合名,oc類內部變量,全局變量,枚舉類型,block類型,結構體類型。 如下分別例舉了幾種類型的例子。github

類名
  1. 使用類前綴
  2. 須要包含一個名詞用來表示這個類是什麼,好比 NSString, NSDate, NSScanner等。
@interface MKUserTrackingBarButtonItem : UIBarButtonItem
協議名
  1. 使用類前綴
  2. 在這裏咱們須要考慮一個重要的問題,不要濫用關鍵字,。面列了兩個協議,"delegate"一般用於實現委託功能,而第二個用於實現的重載。
  3. 大部分協議實際是包括一組功能相關的函數,而且和具體用於實現的類沒有特別緊密的聯繫。這時候命名要考慮和具體實現類區分開,好比起名爲NSLocking而不是NSLock。
  4. 還有一些協議實際上囊括了不少不相關的功能(或者說像是不少個子協議的組合),這時候就能夠和具體的實現類保持一致的名字,好比NSObject。
@protocol MKMapViewDelegate <NSObject>
@protocol MKAnnotation <NSObject>
組合名
  1. 須要類前綴
@interface NSString (NSStringExtensionMethods)
oc類內部變量
  1. 無需類前綴
  2. 儘量使用property定義變量
  3. .對於一些BOOL型變量表示狀態的通常是動詞+時態來表示一個名詞,好比loading和selected。有趣的是,他們的getter方法都寫成了 is+變量名,這樣用起來的時候就更加接近天然語言。
@property (nonatomic, copy) NSString *title;

@property (nonatomic, readonly, getter=isLoading) BOOL loading;
@property (nonatomic, getter=isSelected) BOOL selected;
全局變量
  1. 必須添加類前綴
  2. 對於全局通知,咱們須要遵照一個標準結構:「[Name of associated class] + [Did | Will] + [UniquePartOfName] + Notification」
NSString * const NSSystemClockDidChangeNotification
枚舉
  1. 類型名及枚舉值均須要添加類前綴
  2. 枚舉的具體值的名字爲 枚舉名+名詞
  3. 使用enum枚舉,由於它支持強類型檢查及自動完成。SDK如今也支持枚舉定義宏NS_ENUm()NS_OPTION(),前者的各個選項是互斥的,然後者能夠經過按位或|來組合使用。
typedef NS_OPTIONS(NSUInteger, MKDirectionsTransportType) {
  MKDirectionsTransportTypeAutomobile     = 1 << 0,
  MKDirectionsTransportTypeWalking        = 1 << 1,
  MKDirectionsTransportTypeAny            = 0x0FFFFFFF
}
block
  1. 形參名無需前綴,類型名須要添加前綴
  2. 蘋果的習慣是以handler結尾表示他的功能
typedef void (^MKDirectionsHandler)(MKDirectionsResponse *response, NSError *error);
結構體
  1. 形參名無需前綴,類型名須要添加前綴
typedef struct {
	CLLocationDegrees latitude;
	CLLocationDegrees longitude;
} CLLocationCoordinate2D;
通用規則
  1. 禁止使用小寫下劃線形式(snake_case)
  2. 關於類前綴這件事情,對於全局可見的變量須要添加,而對於類的內部變量和結構體內部變量則不須要添加。咱們概括一個原則,即變量的從屬關係。對於能夠全局可見的類型(類名,協議名,組合名,全局變量,枚舉類型,block類型,結構體名)從屬於項目名下,因爲項目自己沒法添加命名空間,即全部屬於他名下的變量名須要添加前綴。而類的內部變量從屬於他的所屬類,結構體內部變量從屬於結構體自己。
  3. 這個原則是討論在考慮層級的原則下如何給變量名起一個合適的名字。上面咱們討論了從屬規則,爲了統一原則,咱們將有從屬的變量和他的從屬合併。好比MKUserTrackingBarButtonItem類內部有個NSString變量叫title,咱們就合併爲MKUserTrackingBarButtonItemTitle。對於絕大部分事物咱們均可以認爲他是名詞或者形容詞加名詞,在這裏諸如userTracking,barButtonItem,title,在一個項目中爲了準確的標示一個變量是什麼就須要從他的前綴開始逐層向下看他的每一個層級是否能準確的標示這個層級是什麼。就像上述例子,userTracking是全局惟一的事物,這裏的barButtonItem只屬於userTracking,而這裏的headline又只屬於UserTrackingBarButtonItem,這樣咱們能夠很明顯的看出這個title準確的標示着什麼。
  4. 這裏討論一下關於單個層級的命名原則,上面論述過能夠把變量拆分爲幾個層級。對於每一個層級來講咱們傾向於爲一個名詞或者名詞詞組,在使用詞組時不添加介詞,好比寫成nameLabel而不是labelForName。在描述一個層級的時候須要考慮幾個問題,是什麼,實現什麼功能,在什麼狀況下實現這個功能。而後反序寫出來 會變成:限定詞+功能+類型 這樣一種組合方式。固然這三部分在某些狀況下均可以缺省,這個放到後面論述。
  5. 這裏討論在選擇用來命名的單詞的問題。其實到這裏纔到了真正的關鍵點,命名用詞的選擇!依據apple官方的要求,這裏總結了幾點。 ######清晰 1.官方對於清晰的要求是不要濫用縮寫 好比destinationSelection 不要寫成destSel。至於什麼時候能用縮寫咱們下面討論。 2.注意用詞是否有一個明確的含義,當諸如object,data,flag單獨做爲變量出現的時候,確定讓人無所適從。 ######一致 當不少個類有一樣做用的變量時,應該保證他們使用同一個變量名。好比tag用於NSView, NSCell, NSControl中。 ######使用縮寫 在前面的「清晰」的要求中提出了不要濫用縮寫,那何時推薦用縮寫呢?這裏有一個準則是使用你們默認的縮寫,在apple的官方文檔中有一個例子(見下方連接【apple使用的縮寫】)。 固然這裏不要求全部非一下詞彙不可以使用縮寫,咱們這裏但願達成幾個準則爲: 1.縮寫不會和別的詞彙產生混淆和衝突, 假設咱們把Matrix簡寫成mtx就很容易和其餘詞(好比max,mix)產生混淆。 2.在項目中要足夠經常使用 3.若是使用就保持全局統一使用,不要同時出現全稱和縮寫 4.和團隊成員達成統一

apple使用的縮寫objective-c

######一些習慣 1.上述咱們說一個層級變量起名爲 限定詞+功能+類型。這裏咱們有個例外的地方,對於NSString, NSArray, NSNumber, BOOL類型咱們無需指定類型。安全

2.對於命名一個複數形式的變量,若是它不是NSArray或者NSSet最好指定類型。

3.對於其餘類型,好比Image, Indicator這樣的特殊類型或UI組件在變量命名的後半部分指定它的類型是有必要的。 尤爲對於XXXManager類型的變量寫成好比fontManager是必須的,不然沒法理解它的含義。

一些習慣的例子app

函數名

oc語言實際上很貼近天然語言。先拋開一般做爲全局函數用的c/cpp函數,oc的類內部函數一般看起來就像是一個句子。在這個命名規範裏不去結合語法分析這個了,一下會根據函數常有的功能去作個分類。iphone

#####以動詞開頭方法ide

- (void)insertOverlay:(id <MKOverlay>)overlay atIndex:(NSUInteger)index level:(MKOverlayLevel)level;

- (BOOL)createSymbolicLinkAtURL:(NSURL *)url withDestinationURL:(NSURL *)destURL error:(NSError **)error;
1.對於以動詞開頭的函數,表示去執行某一個任務。
2.咱們通常定義他的返回值爲void, 當須要獲得他是否執行成功的狀態時能夠以BOOL做爲返回值
3.有一個例外是init開頭通常是用於構造,返回一個構造的實例。

#####以名詞開頭方法svn

- (MKAnnotationView *)viewForAnnotation:(id <MKAnnotation>)annotation;

+ (instancetype)circleWithCenterCoordinate:(CLLocationCoordinate2D)coord
                                  radius:(CLLocationDistance)radius;
1.對於以動詞開頭的函數,表示返回某一個具體事物。
2.當函數做爲回調函數存在時,它是例外的

#####回調函數

- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row;
- (BOOL)applicationOpenUntitledFile:(NSApplication *)sender;

- (void)browserDidScroll:(NSBrowser *)sender;
- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window;

- (BOOL)windowShouldClose:(id)sender;
          
- (void)windowDidChangeScreen:(NSNotification *)notification;
1.消息發送者必須做爲參數。若是函數參數只有消息發送者自己,將他放到函數最後。若是有2個及以上參數則放到第一位。
2.did和will常常用在回調函數當作標記'已經發生'或者'將要發生'。
3.'should'應用場景一般是詢問代理行爲是否應該發生,一般返回BOOL。
4.經過通知的回調通常來講全部數據都放在notification內部,因此不須要返回值以及其餘參數
5.全部回調函數均以名詞開頭,標示是什麼引起的回調。咱們能夠認爲去掉開頭的名詞和調用者參數,基本和咱們以前定的規範一致。

#####一些通用規則和建議 1.參數冒號以前用名詞指代明確的參數類型 2.多個參數不須要用and鏈接 3.一些介詞有助於提高函數名的可讀性,好比:for,with,from,in,on,at等。

####處理魔術變量 使用常量而非內聯的字串literal或魔術數,由於這樣能更方便地修改它們。
使用static const常量,禁止使用#define宏來定義變量,使用宏沒有類型檢查,並易被覆蓋定義而很難檢測。

For example:

static NSString * const ZDAboutViewControllerCompanyName = @"The New York Times Company";
static const CGFloat ZDAboutViewThumbImageHeight = 50.0;

Not:

#define CompanyName @"The ZDWorks Company"
#define thumbnailHeight 2

Dot-notation

使用dot-notation(.表示法)來獲取/更改property。 Bracket notation([]表示法)適用於其餘領域。
For example:

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

Not:

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

@property && ivar

只能在初始化方法、析構方法和自定義getter/setter裏面,直接訪問實例變量(ivar),其餘狀況只能經過dot-notation訪問property。更多內容參見 here. 不要直接聲明實例變量,聲明property便可。

格式

工具

使用BBUncrustify來格式化代碼,formatter使用Clang,配置文件見.clang-format

Spacing

  • 使用4個空格而非tab符縮進,並檢查其是否爲Xcode預設值。
  • 方法的大括號另起一行打開({),另起一行關閉(})
  • 其餘大括號 (if/else/switch/while/block etc.)在當前行打開,另起一行關閉

For example:

- (void)foo:(User *)user
{
    if (user.isHappy) {
    //Do something
    }
    else {
    //Do something else
    }
}
  • 方法之間隔一個空行。方法內依據功能的不一樣,用空行隔開,或者將其提取到新方法內。
  • 每一個@dynamic@synthesize佔據一行,Xcode4.4之後省略@synthesize

條件語句

條件語句的body必須被括號包含,即便只有一行。這樣便於在body內新增操做而不會出錯,同時可讀性更強。

For example:

if (!error) {
    return success;
}

Not:

if (!error)
    return success;

or

if (!error) return success;

方法

OC的方法,須要在符號+/-後添加一個空格。前一個參數和後一箇中綴之間有且僅有一個空格,好比下方示例的text參數和image中綴之間。

For Example:

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

變量

指針變量的*與指針類型中間隔一個空格,與變量名中間無空格,e.g., NSString *text not NSString* text or NSString * text

註釋

原則

對外接口必須寫註釋

註釋的類型

註釋能夠採用/* *///兩種註釋符號,涉及到多行註釋時,儘可能使用/* */。方法裏的註釋只能使用//,由於嵌套/**/極可能帶來沒法預知的問題。

Xcode會生成一段默認註釋,咱們須要在此基礎上擴充,加入功能描述和修改記錄部分。雖然svn/git可以看到完整的修改記錄以及經過blame查找責任人,可是commit太多的時候很難定位。
For example:

//
//  ClockTricksController.m
//  ZDClock
//
//  Created by John_Ma on 13-11-26.
//  Copyright (c) 2013年 ZDworks Co., Ltd. All rights reserved.
//  功能描述:
//  修改記錄:(僅記錄功能修改)
//       張三   2012-02-02  建立該單元 
//       小明   2010-03-02  增長本地點單功能。
//

方法

方法註釋通常出如今.h文件裏,.m文件裏儘可能保持簡潔,使用方法名完整描述功能和參數。方法註釋使用VVDocument插件生成,並在每次修改後及時更新。
For example:

/**
 *  <#Description#>
 *
 *  @param application   <#application description#>
 *  @param launchOptions <#launchOptions description#>
 *
 *  @return <#return value description#>
 */

其餘

儘可能不要出現方法內註釋,若有可能將相關代碼Extract到新方法裏,使用方法名描述其功能。若是必需要要使用方法內註釋,使用//註釋在所要描述的代碼前一行或者同一行末尾。

最佳實踐

@interface

.h文件中只暴露目前被其餘類使用的接口、屬性。內部使用的接口、屬性在extension(匿名category)中定義,好比IBOutlet等。
在.h實現protocol亦是如此,會暴露該protocol包含的接口。若是外部無需使用相關接口,則移到extesion中。

For example:

// .m file
@interface Test ()<UITableViewDataSource>
@property (nonatomic, strong) IBOutlet UIButton *refreshButton;
- (void)privateDoSth;
@end

Not:

// .m file
@interface Test : NSObject<UITableViewDataSource>
@property (nonatomic, strong) IBOutlet UIButton *refreshButton;
- (void)privateDoSth;
@end

三目運算符

只有當可以提升代碼的可讀性時,才應該使用三目運算符?:,好比單一判斷條件。若是有多個判斷條件,使用if會更好些。

For example:

result = a > b ? x : y;

Not:

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

錯誤處理

當方法使用引用返回表示錯誤的參數時,使用返回值判斷,而非該錯誤變量。

For example:

NSError *error;
if (![self trySomethingWithError:&error]) {
    // Handle Error
}

Not:

NSError *error;
[self trySomethingWithError:&error];
if (error) {
    // Handle Error
}

在成功的狀況下,Apple的一些API會將奇怪的值而非nil寫入錯誤參數,因此不要使用該錯誤變量來判斷。

Literals

NSString, NSDictionary, NSArrayNSNumber的immutable實例應該使用literal來建立,mutable實例也建議經過這種方式及mutableCopy方法來建立。須要注意的是須要作nil檢測。

For example:

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

Not:

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 Functions

使用CGGeometry functions而非結構體的數據成員來獲取x, y, width, or height的值。From Apple's CGGeometry reference:

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結構體做爲輸入參數的方法,先對這些矩形作標準化操做,再計算它們的方繪製。因此咱們應該直接經過這些方法,而非訪問結構體的數據成員來得到這些矩形的屬性。

For example:

CGRect frame = self.view.frame;

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

Not:

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;

私有Properties

私有property應該定義在類擴展(匿名類別)中。這樣有個好處是,當你須要將其暴露給外部,直接command+x、command+v到.h文件中便可。

For example:

@interface ZDAdvertisement ()

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

@end

單例

在OC中,使用以下線程安全的方式來建立單例

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

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

   return sharedInstance;
}

這種方式能夠防止 可能的崩潰.

代碼組織

函數長度(行數)不該超過2/3屏幕,禁止超過70行。
例外:對於順序執行的初始化函數,若是其中的過程沒有提取爲獨立方法的必要,則沒必要限制長度。

  • 單個文件方法數不該超過30個
  • 不要按類別排序(如把IBAction放在一塊),應按任務把相關的組合在一塊兒
  • 禁止出現超過兩層循環的代碼,用函數或block替代。

儘早返回錯誤:

For example:

- (Task *)creatTaskWithPath:(NSString *)path {
    if (![path isURL]) {
        return nil;
    }
    
    if (![fileManager isWritableFileAtPath:path]) {
        return nil;
    }
    
    if ([taskManager hasTaskWithPath:path]) {
        return nil;
    }
    
    Task *aTask = [[Task alloc] initWithPath:path];
    return aTask;
}

Not:

- (Task *)creatTaskWithPath:(NSString *)path {
    Task *aTask;
    if ([path isURL]) {
        if ([fileManager isWritableFileAtPath:path]) {
            if (![taskManager hasTaskWithPath:path]) {
                aTask = [[Task alloc] initWithPath:path];
            }
            else {
                return nil;
            }
        }
        else {
            return nil;
        }
    }
    else {
        return nil;
    }
    return aTask;
}
相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息