iOS團隊編程規范

前 言

  • 需求是暫時的,只有變化才是永恒的,面向變化編程,而不是面向需求編程。

  • 不要過分追求技巧,降低程序的可讀性。

  • 簡潔的代碼可以讓bug無處藏身。要寫出明顯沒有bug的代碼,而不是沒有明顯bug的代碼。

  • 先把眼前的問題解決掉,解決好,再考慮將來的擴展問題。

一、命名規范

1、統一要求

含義清楚,盡量做到不需要注釋也能了解其作用,若做不到,就加注釋,使用全稱,不使用縮寫。

2、類名

大駝峰式命名:每個單詞的首字母都采用大寫字母

==例:== MFHomePageViewController

3、私有變量

  • 私有變量放在 .m 文件中聲明

  • 以 _ 開頭,第一個單詞首字母小寫,后面的單詞的首字母全部大寫。

==例:== NSString *_somePrivateVariable

4、property變量

  • 小駝峰式命名:第一個單詞以小寫字母開始,后面的單詞的首字母全部大寫

  • 屬性的關鍵字推薦按照 原子性,讀寫,內存管理的順序排列。

  • BlockNSString屬性應該使用copy關鍵字

  • 禁止使用synthesize關鍵詞

==例:==

typedef void (^ErrorCodeBlock) (id errorCode,NSString *message);

@property (nonatomic, readwrite, strong) UIView *headerView;    //注釋

@property (nonatomic, readwrite, copy) ErrorCodeBlock errorBlock;   //將block拷貝到堆中
@property (nonatomic, readwrite, copy) NSString *userName;    

5、宏和常量命名

  • 對于宏定義的常量

    • #define 預處理定義的常量全部大寫,單詞間用 _ 分隔
    • 宏定義中如果包含表達式或變量,表達式或變量必須用小括號括起來。
  • 對于類型常量

    • 對于局限于某編譯單元(實現文件)的常量,以字符k開頭,例如kAnimationDuration,且需要以static const修飾
    • 對于定義于類頭文件的常量,外部可見,則以定義該常量所在類的類名開頭,例如EOCViewClassAnimationDuration, 仿照蘋果風格,在頭文件中進行extern聲明,在實現文件中定義其值

==例:==

//宏定義的常量
#define ANIMATION_DURATION    0.3
#define MY_MIN(A, B)  ((A)>(B)?(B):(A))

//局部類型常量
static const NSTimeInterval kAnimationDuration = 0.3;

//外部可見類型常量
//EOCViewClass.h
extern const NSTimeInterval EOCViewClassAnimationDuration;
extern NSString *const EOCViewClassStringConstant;  //字符串類型

//EOCViewClass.m
const NSTimeInterval EOCViewClassAnimationDuration = 0.3;
NSString *const EOCViewClassStringConstant = @"EOCStringConstant";

6、Enum

  • Enum類型的命名與類的命名規則一致

  • Enum中枚舉內容的命名需要以該Enum類型名稱開頭

  • NS_ENUM定義通用枚舉,NS_OPTIONS定義位移枚舉

==例:==

typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {
    UIViewAnimationTransitionNone,
    UIViewAnimationTransitionFlipFromLeft,
    UIViewAnimationTransitionFlipFromRight,
    UIViewAnimationTransitionCurlUp,
    UIViewAnimationTransitionCurlDown,
};

typedef NS_OPTIONS(NSUInteger, UIControlState) {
    UIControlStateNormal       = 0,
    UIControlStateHighlighted  = 1 << 0,
    UIControlStateDisabled     = 1 << 1,
};

7、Delegate

  • delegate做后綴,如<UIScrollViewDelegate>

  • optional修飾可以不實現的方法,用required修飾必須實現的方法

  • 當你的委托的方法過多, 可以拆分數據部分和其他邏輯部分, 數據部分用dataSource做后綴. 如<UITableViewDataSource>

  • 使用didwill通知Delegate已經發生的變化或將要發生的變化。

  • 類的實例必須為回調方法的參數之一

    1. 回調方法的參數只有類自己的情況,方法名要符合實際含義
    2. 回調方法存在兩個以上參數的情況,以類的名字開頭,以表明此方法是屬于哪個類的

==例:==

@protocol UITableViewDataSource<NSObject>

@required

//回調方法存在兩個以上參數
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;

@optional

//回調方法的參數只有類自己
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;              // Default is 1 if not implemented
@protocol UITableViewDelegate<NSObject, UIScrollViewDelegate>

@optional

//使用`did`和`will`通知`Delegate`
- (nullable NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;

8、方法

  • 方法名用小駝峰式命名

  • 方法名不要使用new作為前綴

  • 不要使用and來連接屬性參數,如果方法描述兩種獨立的行為,使用and來串接它們。

  • 方法實現時,如果參數過長,則令每個參數占用一行,以冒號對齊。

  • 一般方法不使用前綴命名,私有方法可以使用統一的前綴來分組和辨識

  • 方法名要與對應的參數名保持高度一致

  • 表示對象行為的方法、執行性的方法應該以動詞開頭

  • 返回性的方法應該以返回的內容開頭,但之前不要加get,除非是間接返回一個或多個值。

  • 可以使用情態動詞(動詞前面can、should、will等)進一步說明屬性意思,但不要使用dodoes,因為這些助動詞沒什么實際意義。也不要在動詞前使用副詞或形容詞修飾

==例:==

//不要使用 and 來連接屬性參數
- (int)runModalForDirectory:(NSString *)path file:(NSString *)name types:(NSArray *)fileTypes;    //推薦
- (int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes;    //反對

//表示對象行為的方法、執行性的方法
- (void)insertModel:(id)model atIndex:(NSUInteger)atIndex;
- (void)selectTabViewItem:(NSTableViewItem *)tableViewItem

//返回性的方法
- (instancetype)arrayWithArray:(NSArray *)array;

//參數過長的情況
- (void)longMethodWith:(NSString *)theFoo
                  rect:(CGRect)theRect
              interval:(CGFloat)theInterval
{
   //Implementation
}

//不要加get
- (NSSize) cellSize;  //推薦
- (NSSize) getCellSize;  //反對

//使用情態動詞,不要使用do或does
- (BOOL)canHide;  //推薦
- (BOOL)shouldCloseDocument;  //推薦
- (BOOL)doesAcceptGlyphInfo;  //反對

二、代碼注釋規范

優秀的代碼大部分是可以自描述的,我們完全可以用代碼本身來表達它到底在干什么,而不需要注釋的輔助。

但并不是說一定不能寫注釋,有以下三種情況比較適合寫注釋:

  • 公共接口(注釋要告訴閱讀代碼的人,當前類能實現什么功能)。

  • 涉及到比較深層專業知識的代碼(注釋要體現出實現原理和思想)。

  • 容易產生歧義的代碼(但是嚴格來說,容易讓人產生歧義的代碼是不允許存在的)。

除了上述這三種情況,如果別人只能依靠注釋才能讀懂你的代碼的時候,就要反思代碼出現了什么問題。

最后,對于注釋的內容,相對于“做了什么”,更應該說明“為什么這么做”

1、import注釋

如果有一個以上的import語句,就對這些語句進行分組,每個分組的注釋是可選的。

// Frameworks
#import <QuartzCore>;

// Models
#import "NYTUser.h"

// Views
#import "NYTButton.h"
#import "NYTUserView.h"

2、屬性注釋

寫在屬性之后,用兩個空格隔開
==例:==

@property (nonatomic, readwrite, strong) UIView *headerView;  //注釋    

3、方法聲明注釋:

一個函數(方法)必須有一個字符串文檔來解釋,除非它:

  • 非公開,私有函數。
  • 很短。
  • 顯而易見。

而其余的,包括公開接口,重要的方法,分類,以及協議,都應該伴隨文檔(注釋):

  • 以/開始
  • 第二行是總結性的語句
  • 第三行永遠是空行
  • 在與第二行開頭對齊的位置寫剩下的注釋。

建議這樣寫:

/This comment serves to demonstrate the format of a doc string.

Note that the summary line is always at most one line long, and after the opening block comment,
and each line of text is preceded by a single space.
*/

方法的注釋使用Xcode自帶注釋快捷鍵:Commond+option+/
==例:==

/**
 <#Description#>

 @param tableView <#tableView description#>
 @param section <#section description#>
 @return <#return value description#>
 */
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
    //...
}

4、代碼塊注釋

單行的用//+空格開頭,多行的采用/* */注釋

5、TODO

使用//TODO:說明 標記一些未完成的或完成的不盡如人意的地方

==例:==

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    //TODO:增加初始化
    return YES;
}

三、代碼格式化規范

1、指針*位置

定義一個對象時,指針*靠近變量

==例:== NSString *userName;

2、方法的聲明和定義

- 、+和 返回值之間留一個空格,方法名和第一個參數之間不留空格

==例:==

- (void)insertSubview:(UIView *)view atIndex:(NSInteger)index;

3、代碼縮進

  • 不要在工程里使用 Tab 鍵,使用空格來進行縮進。在 Xcode > Preferences > Text Editing 將 Tab 和自動縮進都設置為 4 個空格

  • MethodMethod之間空一行

  • 一元運算符與變量之間沒有空格、二元運算符與變量之間必須有空格

==例:==

!bValue
fLength = fWidth * 2;

- (void)sampleMethod1;

- (void)sampleMethod2;

4、對method進行分組

使用#pragma mark -method進行分組

#pragma mark - Life Cycle Methods
- (instancetype)init
- (void)dealloc

- (void)viewWillAppear:(BOOL)animated
- (void)viewDidAppear:(BOOL)animated
- (void)viewWillDisappear:(BOOL)animated
- (void)viewDidDisappear:(BOOL)animated

#pragma mark - Override Methods

#pragma mark - Intial Methods

#pragma mark - Network Methods

#pragma mark - Target Methods

#pragma mark - Public Methods

#pragma mark - Private Methods

#pragma mark - UITableViewDataSource  
#pragma mark - UITableViewDelegate  

#pragma mark - Lazy Loads

#pragma mark - NSCopying  

#pragma mark - NSObject  Methods

5、大括號寫法

  • 對于類的method:左括號另起一行寫(遵循蘋果官方文檔)

  • 對于其他使用場景(if,for,while,switch等): 左括號跟在第一行后邊

==例:==

- (void)sampleMethod
{
    BOOL someCondition = YES;
    if(someCondition) {
        // do something here
    }
}

6、property變量

==例:==

@property (nonatomic, readwrite, strong) UIView *headerView;    //注釋    

四、編碼規范

1、if語句

①、須列出所有分支(窮舉所有的情況),而且每個分支都須給出明確的結果。

==推薦這樣寫:==

var hintStr;
if (count < 3) {
  hintStr = "Good";
} else {
  hintStr = "";
}

==不推薦這樣寫:==

var hintStr;
if (count < 3) {
 hintStr = "Good";
}

②、不要使用過多的分支,要善于使用return來提前返回錯誤的情況,把最正確的情況放到最后返回。

==推薦這樣寫:==

if (!user.UserName) return NO;
if (!user.Password) return NO;
if (!user.Email) return NO;

return YES;

==不推薦這樣寫:==

BOOL isValid = NO;
if (user.UserName)
{
    if (user.Password)
    {
        if (user.Email) isValid = YES;
    }
}
return isValid;

③、條件過多,過長的時候應該換行。條件表達式如果很長,則需要將他們提取出來賦給一個BOOL值,或者抽取出一個方法

==推薦這樣寫:==

if (condition1 && 
    condition2 && 
    condition3 && 
    condition4) {
  // Do something
}
BOOL finalCondition = condition1 && condition2 && condition3 && condition4
if (finalCondition) {
  // Do something
}
if ([self canDelete]){
  // Do something
}

- (BOOL)canDelete
{
    BOOL finalCondition1 = condition1 && condition2
    BOOL finalCondition2 =  condition3 && condition4

    return condition1 && condition2;
}

==不推薦這樣寫:==

if (condition1 && condition2 && condition3 && condition4) {
  // Do something
}

④、條件語句的判斷應該是變量在右,常量在左。

==推薦:==

if (6 == count) {
}

if (nil == object) {
}

if (!object) {
}

==不推薦:==

if (count == 6) {
}

if (object == nil) {
}

if (object == nil)容易誤寫成賦值語句,if (!object)寫法很簡潔

⑤、每個分支的實現代碼都須被大括號包圍

==推薦:==

if (!error) {
  return success;
}

==不推薦:==

if (!error)
    return success;

可以如下這樣寫:

if (!error) return success;

2、for語句

①、不可在for循環內修改循環變量,防止for循環失去控制。

for (int index = 0; index < 10; index++){
   ...
   logicToChange(index)
}

②、避免使用continue和break。

continuebreak所描述的是“什么時候不做什么”,所以為了讀懂二者所在的代碼,我們需要在頭腦里將他們取反。

其實最好不要讓這兩個東西出現,因為我們的代碼只要體現出“什么時候做什么”就好了,而且通過適當的方法,是可以將這兩個東西消滅掉的:

  • 如果出現了continue,只需要把continue的條件取反即可
var filteredProducts = Array<String>()
for level in products {
    if level.hasPrefix("bad") {
        continue
    }
    filteredProducts.append(level)
}

我們可以看到,通過判斷字符串里是否含有“bad”這個prefix來過濾掉一些值。其實我們是可以通過取反,來避免使用continue的:

for level in products {
    if !level.hasPrefix("bad") {
      filteredProducts.append(level)
    }
}
  • 消除while里的break:break的條件取反,并合并到主循環里

while里的break其實就相當于“不存在”,既然是不存在的東西就完全可以在最開始的條件語句中將其排除。

while里的break:

while (condition1) {
  ...
  if (condition2) {
    break;
  }
}

取反并合并到主條件:

while (condition1 && !condition2) {
  ...
}
  • 在有返回值的方法里消除break:break轉換為return立即返回

有人喜歡這樣做:在有返回值的方法里break之后,再返回某個值。其實完全可以在break的那一行直接返回。

func hasBadProductIn(products: Array<String>) -> Bool {

    var result = false    
    for level in products {
        if level.hasPrefix("bad") {
            result = true
            break
        }
    }
   return result
}

遇到錯誤條件直接返回:

func hasBadProductIn(products: Array<String>) -> Bool {
    for level in products {
        if level.hasPrefix("bad") {
            return true
        }
    }
   return false
}

這樣寫的話不用特意聲明一個變量來特意保存需要返回的值,看起來非常簡潔,可讀性高。

3、Switch語句

①、每個分支都必須用大括號括起來

推薦這樣寫:

switch (integer) {  
  case 1:  {
    // ...  
   }
    break;  
  case 2: {  
    // ...  
    break;  
  }  
  default:{
    // ...  
    break; 
  }
}

②、使用枚舉類型時,不能有default分支, 除了使用枚舉類型以外,都必須有default分支

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

Switch語句使用枚舉類型的時候,如果使用了default分支,在將來就無法通過編譯器來檢查新增的枚舉類型了。

4、函數

①、一個函數只做一件事(單一原則)

每個函數的職責都應該劃分的很明確(就像類一樣)。

==推薦:==

dataConfiguration()
viewConfiguration()

==不推薦:==

void dataConfiguration()
{   
   ...
   viewConfiguration()
}

②、對于有返回值的函數(方法),每一個分支都必須有返回值

==推薦:==

int function()
{
    if(condition1){
        return count1
    }else if(condition2){
        return count2
    }else{
       return defaultCount
    } 
}

==不推薦:==

int function()
{
    if(condition1){
        return count1
    }else if(condition2){
        return count2
    }
}

③、對輸入參數的正確性和有效性進行檢查,參數錯誤立即返回

==推薦:==

void function(param1,param2)
{
      if(param1 is unavailable){
           return;
      }

      if(param2 is unavailable){
           return;
      }

     //Do some right thing
}

④、如果在不同的函數內部有相同的功能,應該把相同的功能抽取出來單獨作為另一個函數

原來的調用:

void logic() {
  a();
  b();
  if (logic1 condition) {
    c();
  } else {
    d();
  }
}

將a,b函數抽取出來作為單獨的函數

void basicConfig() {
  a();
  b();
}

void logic1() {
  basicConfig();
  c();
}

void logic2() {
  basicConfig();
  d();
}

⑤、將函數內部比較復雜的邏輯提取出來作為單獨的函數

一個函數內的不清晰(邏輯判斷比較多,行數較多)的那片代碼,往往可以被提取出去,構成一個新的函數,然后在原來的地方調用它這樣你就可以使用有意義的函數名來代替注釋,增加程序的可讀性。

舉一個發送郵件的例子:

openEmailSite();
login();

writeTitle(title);
writeContent(content);
writeReceiver(receiver);
addAttachment(attachment);

send();

中間的部分稍微長一些,我們可以將它們提取出來:

void writeEmail(title, content,receiver,attachment)
{
  writeTitle(title);
  writeContent(content);
  writeReceiver(receiver);
  addAttachment(attachment); 
}

然后再看一下原來的代碼:

openEmailSite();
login();
writeEmail(title, content,receiver,attachment)
send();

參考資料:
iOS 代碼規范
iOS開發總結之代碼規范
iOS開發代碼規范(通用)
Objective-C開發編碼規范
【iOS】命名規范
Ios Code Specification
Apple Coding Guidelines for Cocoa
Google Objective-C Style Guide

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

推薦閱讀更多精彩內容

  • 原文: https://github.com/ecomfe/spec/blob/master/javascript...
    zock閱讀 3,391評論 2 36
  • 這幾天,一直在下雨,細雨蒙蒙,仿若似水的柔情,綿綿不斷。而恰恰在這種日子里,我就會更加想念你。都說人生要懂得放下,...
    水瓶輝偉閱讀 261評論 0 0
  • 我自那荒島奔逃. 島里住著倆發小,一個叫文藻,一個叫心橋,我自那荒島奔逃. 我自那荒島奔逃,只因他兩人癡纏,讓我吃...
    克羅諾皮奧閱讀 217評論 0 2
  • flyingtoparis閱讀 133評論 0 0
  • 朋友發圈說自己今天正式進入不惑之年,青春已不再。我回復“華麗麗的人生才剛剛開始”。隨口而出的一句話聽起來很勵志,回...
    866b9aee215f閱讀 159評論 0 0