小膚iOS開發代碼規范_v1.0

For Objective-C , 2018.8.2

Ⅰ.前言
Ⅱ.命名規范
Ⅲ.代碼注釋規范
Ⅳ.代碼格式化規范
Ⅴ.編碼規范
Ⅵ.參考資料

Ⅰ.前言


1.需求是暫時的,只有變化才是永恒的,面向變化編程,而不是面向需求編程。
2.不要過分追求技巧,降低程序的可讀性。
3.簡潔的代碼可以讓bug無處藏身。要寫出明顯沒有bug的代碼,而不是沒有明顯bug的代碼。
4.先把眼前的問題解決掉,解決好,再考慮將來的擴展問題。



Ⅱ.命名規范


1、統一要求
含義清楚,盡量做到不需要注釋也能了解其作用,若做不到,就加注釋,使用全稱,不使用縮寫。
2、類名
大駝峰式命名:每個單詞的首字母都采用大寫字母。
示例:XFHomePageViewController
3、property變量
小駝峰式命名:第一個單詞以小寫字母開始,后面的單詞的首字母全部大寫,
屬性的關鍵字推薦按照 原子性,讀寫,內存管理 的順序排列,
BlockNSString屬性應該使用copy關鍵字,
禁止使用synthesize關鍵詞,
可以不暴露在.h文件中的property變量,盡量寫在.m文件中,保證.h文件的簡潔。
示例:

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;

4、私有變量
私有變量放在.m 文件中聲明,以_開頭,第一個單詞首字母小寫,后面的單詞的首字母全部大寫,
示例:NSString *_somePrivateVariable
ps:一般的,也可以使用property變量代替私有變量,這樣可能更便于編碼時使用點語法 / 懶加載等;
5.一般變量和對象的命名
給一個對象命名時建議采用 修飾 + 類型 的方式,如果只用修飾命名會引起歧義,比如title (這個到底是個NSString還是UILabel?),同樣的,如果只用類型來命名則會缺失作用信息,比如label (ok,我知道你是個UILabel,但是用途呢?),So,正確的命名方式為:
titleLabel //表示標題的label, 是UILabel類型
confirmButton //表示確認的button,是UIButton類型
對于BOOL類型,應加上is前綴,比如:
- (BOOL)isEqualToString:(NSString *)aString 這樣會更加清晰,
如果某方法返回非屬性的BOOL值,那么應根據其功能, 選用hasis當前綴,比如:- (BOOL)hasPrefix:(NSString *)aString
ps:如果某個命名已經很明確了, 為了簡潔可以省去類型名. 比如scores, 很明顯是個array了, 就不必命名成scoreArray了,
6、宏和常量命名
1.對于宏定義的常量
#define預處理定義的常量全部大寫,單詞間用_分隔
重要:宏定義中如果包含表達式或變量,表達式或變量必須用小括號括起來
2.對于類型常量
對于局限于某編譯單元(實現文件)的常量,以字符k開頭,
例如kAnimationDuration,且需要以static const修飾,防止被修改,
對于定義于類頭文件的常量,外部可見,則以定義該常量所在類的類名開頭,例如XFViewClassAnimationDuration, 仿照蘋果風格,在頭文件中進行extern聲明,在實現文件中定義其值,示例:

//宏定義的常量(個別的,如果宏定義個數較多,可以考慮手動左對齊整理)
#define ANIMATION_DURATION   0.3
#define MY_MIN(A, B)         ((A)>(B)?(B):(A))

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

//外部可見類型常量
//XFViewClass.h
extern const NSTimeInterval XFViewClassAnimationDuration;
extern NSString *const      XFViewClassStringConstant;  //字符串類型

//XFViewClass.m
const NSTimeInterval XFViewClassAnimationDuration = 0.3;
NSString *const      XFViewClassStringConstant = @"XFStringConstant";

7、枚舉的命名 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,
};

//不推薦用 C 的方式來定義枚舉類型
typedef enum : {
    CameraModeFront,
    CameraModeLeft,
    CameraModeRight,
} CameraMode;

8、Delegate
delegate做后綴,如<UIScrollViewDelegate>
optional修飾可以不實現的方法,用required修飾必須實現的方法
當你的委托的方法過多, 可以拆分 數據部分其他邏輯 部分, 數據部分用dataSource做后綴. 如<UITableViewDataSource>
使用didwill通知Delegate已經發生的變化或將要發生的變化。
重要:類的實例必須為回調方法的參數之一
回調方法的參數只有類自己的情況,方法名要符合實際含義
回調方法存在兩個以上參數的情況,以類的名字開頭,以表明此方法是屬于哪個類的,示例:

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

9、方法
1.方法名用小駝峰式命名,
2.方法名不要使用new作為前綴,
3.不要使用and來連接屬性參數,如果方法描述兩種獨立的行為,使用and來串接它們,
4.方法實現時,如果參數過長,則令每個參數占用一行,以冒號對齊,
5.一般方法不使用前綴命名,私有方法可以使用統一的前綴來分組和辨識,
6.方法名要與對應的參數名保持高度一致,
7.表示對象行為的方法、執行性的方法應該以動詞開頭,
8.返回性的方法應該以返回的內容開頭,但之前不要加get,除非是間接返回一個或多個值,
9.可以使用情態動詞(動詞前面canshouldwill等)進一步說明屬性意思,但不要使用dodoes,因為這些助動詞沒什么實際意義,也不要在動詞前使用副詞或形容詞修飾,
10.(+開頭的)工廠方法 / 類方法,首字母要大寫,
11.創建Category分類方法時,除了滿足上述要求外,還應注意考慮盡量避免可能在未來發生的方法名沖突的問題,示例:

//不要使用 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;  //反對

//工廠方法 / 類方法 首字母大寫
+ (XFControlManager *)SharedControlManager;

/**
分類方法(Category),比如創建UIImage的分類方法,兩個 
1.創建一張純色圖片
2.獲取圖片中特定點的顏色值
*/
+ (UIImage *)XF_ImageWithColor:(UIColor *)color size:(CGSize)size;
- (UIColor *)xf_ColorAtPixelPoint:(CGPoint)point Alpha:(CGFloat)alpha;



Ⅲ.代碼注釋規范


優秀的代碼大部分是可以自描述的,我們完全可以用代碼本身來表達它到底在干什么,而不需要注釋的輔助。
但并不是說一定不能寫注釋,有以下三種情況比較適合寫注釋:
公共接口(注釋要告訴閱讀代碼的人,當前類能實現什么功能)。
涉及到比較深層專業知識的代碼(注釋要體現出實現原理和思想)。
容易產生歧義的代碼(但是嚴格來說,容易讓人產生歧義的代碼是不允許存在的)。
除了上述這三種情況,如果別人只能依靠注釋才能讀懂你的代碼的時候,就要反思代碼出現了什么問題。
最后,對于注釋的內容,相對于“做了什么”,更應該說明“為什么這么做”。

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

// Frameworks
#import <QuartzCore>;

// Models
#import "XFUser.h"

// Views
#import "XFButton.h"
#import "XFUserView.h"

2、屬性注釋
寫在屬性之后,用兩個空格隔開,示例:

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

3、方法聲明注釋
一個函數(方法)必須有一個字符串文檔來解釋,除非它:
1.非公開,私有函數;2.很短;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:左括號另起一行寫(遵循蘋果官方文檔),
對于其他使用場景(if,for,while,switch等): 左括號跟在第一行后邊,示例:

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

5、對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



Ⅴ.編碼規范


編碼規范簡單來說就是為了保證寫出來的代碼具備三個原則:可復用易維護可擴展,這其實也是面向對象的基本原則; 可復用,簡單來說就是不要寫重復的代碼,有重復的部分要盡量封裝起來重用,否則修改文件的時候得滿地找相同邏輯的地方;易維護,就是不要把代碼復雜化,不要去寫巨復雜邏輯的代碼,而是把復雜的邏輯代碼拆分開一個個小的模塊,這也是Do one thing的概念,每個模塊(或者函數)職責要單一,這樣的代碼會易于維護,也不容易出錯;可擴展,則是要求寫代碼時要考慮后面的擴展需求,這個涉及到架構層面了,利用對應的設計模式來保證,不再此處贅述;

1.賦值

//Preferred
result = object ? : [self createObject];   //條件賦值
BOOL isAdult = age > 18;   //BOOL賦值

//Not preferred
result = object ? object : [self createObject];

BOOL isAdult;
if (age > 18)
{
    isAdult = YES;
}
else
{
    isAdult = NO;
}

2.if條件判斷

1.一般的,須列出所有分支(窮舉所有的情況),而且每個分支都須給出明確的結果

//Preferred
NSString *hintStr;
if (count < 3) {
  hintStr = "Good";
} else {
  hintStr = "";
}

//Not preferred
NSString *hintStr;
if (count < 3) {
  hintStr = "Good";
}

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

//Preferred
if ([self canDeleteJob:job]) { ... }        
 
- (BOOL)canDeleteJob:(Job *)job
{
    BOOL invalidJobState = job.JobState == JobState.New
                          || job.JobState == JobState.Submitted
                          || job.JobState == JobState.Expired;
    BOOL invalidJob = job.JobTitle && job.JobTitle.length;
 
    return invalidJobState || invalidJob;
}

//Not preferred
if (job.JobState == JobState.New
    || job.JobState == JobState.Submitted
    || job.JobState == JobState.Expired
    || (job.JobTitle && job.JobTitle.length))
{
    //....
}

3.條件語句的判斷應該是變量在右,常量在左

//Preferred
if (6 == count) { ... }
if (nil == someObject) { ... }
if (someObject) { ... } 
if (!someObject) { ... } 

//Not preferred
if (count == 6) { ... }
if (someObject == nil) { ... }
if (someObject == YES) { ... } 
if (someObject != nil) { ... } 

4.特別地,嵌套判斷,不要使用過多的分支,要善于使用return來提前返回錯誤的情況,把最正確的情況放到最后返回,

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

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

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

//Preferred:
if (!error) {
  return success;
}
//或者
if (!error) return success;

//Not preferred
if (!error)
    return success;

3.for語句

1.不可在for循環內修改循環變量,防止for循環失去控制

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

2.避免使用continuebreak
continuebreak所描述的是“什么時候不做什么”,所以為了讀懂二者所在的代碼,我們需要在頭腦里將他們取反。
其實最好不要讓這兩個東西出現,因為我們的代碼只要體現出“什么時候做什么”就好了,而且通過適當的方法,是可以將這兩個東西消滅掉的;

情形1:如果出現了continue:只需要把continue的條件取反即可,

NSMutableArray *filteredProducts = [NSMutableArray array];
for (NSString *level in products) {
    if ([level hasPrefix:@"bad"]) {
       continue;
    }
    [filteredProducts addObject:level];
}

/**
我們可以看到,通過判斷字符串里是否含有“bad”這個prefix來過濾掉一些值,
其實我們是可以通過取反,來避免使用continue的,
*/
NSMutableArray *filteredProducts = [NSMutableArray array];
for (NSString *level in products) {
    if (![level hasPrefix:@"bad"]) {
       [filteredProducts addObject:level];
    }
}

情形2:消除while里的break:將break的條件取反,并合并到主循環里

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

在while里的break其實就相當于“不存在”,
既然是不存在的東西就完全可以在最開始的條件語句中將其排除,
取反并合并到主條件:
*/
while (condition1 && !condition2) {
  ...
}

情形3:在有返回值的方法里消除break:將break轉換為return立即返回

- (BOOL)hasBadProductIn:(NSArray<NSString *> *)products {
    BOOL result = false;
    for (NSString *level in products) {
        if ([level hasPrefix:@"bad"]) {
            result = true;
            break;
        }
    }
    return result;
}

/**
有人喜歡這樣做:在有返回值的方法里break之后,再返回某個值,
其實完全可以在break的那一行直接返回,
遇到錯誤條件直接返回,
*/
- (BOOL)hasBadProductIn:(NSArray<NSString *> *)products {
    for (NSString *level in products) {
        if ([level hasPrefix:@"bad"]) {
            return true;
        }
    }
    return false;
}

4.Switch語句

1.每個分支都必須用大括號括起來

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

2.使用枚舉類型時,不能有default分支, 除了使用枚舉類型以外,都必須有default分支
在Switch語句使用枚舉類型的時候,如果使用了default分支,在將來就無法通過編譯器來檢查新增的枚舉類型了

XFLeftMenuTopItemType menuType = XFLeftMenuTopItemMain;  
switch (menuType) {  
  case XFLeftMenuTopItemMain: {
    // ...  
    break; 
   }
  case XFLeftMenuTopItemShows: {
    // ...  
    break; 
  }
  case XFLeftMenuTopItemSchedule: {
    // ...  
    break; 
  }
}

5.函數

1.一個函數只做一件事(單一原則)
每個函數的職責都應該劃分的很明確(就像類一樣)

//Preferred
[self dataConfiguration];
[self viewConfiguration];

//Not preferred
- (void)dataConfiguration
{   
   ...
   [self viewConfiguration];
}

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

//Preferred
- (int)function
{
    if (condition1) {
        return count1;
    } else if (condition2) {
        return count2;
    } else {
       return defaultCount;
    } 
}

//Not preferred
- (int)function
{
    if (condition1) {
        return count1;
    } else if (condition2) {
        return count2;
    }
}

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

- (void)functionParam1:(id)param1 Param2:(id)param2
{
      if (param1 is unavailable){
           return;
      }

      if (param2 is unavailable){
           return;
      }

     //Do some right thing
}

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

//Preferred:將a,b函數抽取出來作為單獨的函數
- (void)basicConfig {
  [self a];
  [self b];
}

- (void)logic1 {
  [self basicConfig];
  [self c];
}

- (void)logic2 {
  [self basicConfig];
  [self d];
}

//Not preferred
- (void)logic {
  [self a];
  [self b];
  if ([logic1 condition]) {
    [self c];
  } else {
    [self d];
  }
}

5.將函數內部比較復雜的邏輯提取出來作為單獨的函數
一個函數內的不清晰(邏輯判斷比較多,行數較多)的那片代碼,往往可以被提取出去,構成一個新的函數,然后在原來的地方調用它這樣你就可以使用有意義的函數名來代替注釋,增加程序的可讀性,舉一個發送郵件的例子:

//Preferred
- (void)sendEmail
{
    [self openEmailSite];
    [ self login];

   [self writeEmailTitle:title Content:content 
                Receiver:receiver Attachment:attachment];

    [self send];
}

- (void)writeEmailTitle:(id)title Content:(ID)content 
               Receiver:(id)receiver Attachment:(id)attachment
{
    [self writeTitle:title];
    [self writeContent:content];
    [self writeReceiver:receiver];
    [self addAttachment:attachment];
}

//Not preferred
- (void)sendEmail
{
    [self openEmailSite];
    [ self login];

    [self writeTitle:title];
    [self writeContent:content];
    [self writeReceiver:receiver];
    [self addAttachment:attachment];

    [self send];
}



Ⅵ.參考資料


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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容