1. 程式人生 > >iOS Coding Style Guide 程式碼規範

iOS Coding Style Guide 程式碼規範

前言

程式碼規範可以說是老生常談的話題了, 也是程式設計師自我修養的一種體現, 雖然一套好的程式碼規範不能使程式執行的更加流暢, 不能使程式直接的影響到程式的功能執行,但是如果能再發開之前就能明確定義一套程式碼規範,並且嚴格的去執行,肯定能非常有效的提高程式碼閱讀性,高的閱讀性也使得後期開發,維護等事半功倍,上手難度降低,在新人加入進行也能更快的融入團隊。
下面我分別按幾個要素概括列舉一下自己團隊制定的一套程式碼規範,提供大家參考。

命名規範

Coding

我們儘可能遵守 Apple 的命名約定, 其推薦使用長的,描述性強的方法和變數名,使其閱讀起來更加清晰易懂。不能隨意使用縮寫,導致其他人員閱讀程式碼困難。

The coding guide for cocoa

駝峰命名

針對屬性,變數,方法等均採用小寫字母開頭的駝峰命名準則。

字首

專案名稱,類名,檔名都應該保持一致的字首名,根據 Apple Guide 建議類名字首應該使用 2 個英文以上最好,因為 Apple 寫的框架都是直接使用 2 個英文字母開頭, 使用 3 個字母 能有效防止類名重複影響工程。

但是由於專案歷史原因一直採用的是 ==JS== 作為字首, 所以暫時先對這塊不進行改動。 後面如果有新專案的話, 一致採用 ==JSD== 開頭作為字首。

方法命名

根據 Cocoa 命名方法規則,我們應該準守這幾個點。

  1. 使用小寫字母開頭,後面巢狀連線的字母使用大寫開頭。不過在寫Category Method的時候, 我們比較習慣使用 JSD_method 的方式, 而非遵守所有的方法命名規則 jsd_method。 這個我覺得只要統一起來就好了。
  2. 對於採取動作行為的方法,使用動詞開頭,但是不要直接使用 do或者does
  3. 每個方法引數前必須帶有相同或者能清晰表達其原意的關鍵字。
  4. 子類化建立相對父類更加詳細功能的方法時應該把新增引數新增在原有方法的後面。
  5. 假如方法名過長的時候可以採用每個引數獨佔一行的規則,並保持每個引數分號 : 對齊的方式排列。
  6. 例項方法和類方法 (-/+) 符號後面應該保持一個空格, 如: - (void)。

命名屬性和例項變數

  1. 一般屬性一致採用 @property 進行宣告, 特殊的資料型別可以使用例項變數宣告。
  2. @property (nonatomic, copy) NSString *name;
    屬性關鍵字首個應該是 原子性,到記憶體管理關鍵詞。如果需要寫讀寫關鍵字的話, 其排在第二位.如: @property (nonatomic, readwrite, copy) NSString *name;
  3. 宣告例項變數時必須採用 _ 下劃線作為變數名字首。
  4. 新增 @property 相應程式碼段, 提高編碼速度。

命名標頭檔案

  1. 標頭檔案名必須採用一致的字首開頭, 加上其代表相關類名代表的形容詞即可。儘量不要使用縮寫,寧願名字稍微長點兒,也要讓其有一目瞭然的效果。
  2. 宣告相關的類擴充套件和協議時,必須將其宣告放在主類資料夾裡面,這樣閱讀起來相對方便。
  3. 如果有多個功能相似的類,可以考慮將其劃分為一個框架,使用 .h 檔案進行宣告管理。

命名 Category 方法

在寫新增類別方法時, 必須採用字首名_ 後連線對應方法名的方式來進行新增。這樣就有效的避免,覆蓋掉系統原有或新增方法, 導致意想不到的問題發生。
如: - (void)JS_methodName - (void)JSD_methodName

統一的ViewController 模板

針對大多數沒有特別複雜邏輯的控制器實現檔案, 我們制定了一套 @implementation 實現模板.
如下:

#import "JSDInvestViewController.h"

@interface JSDInvestViewController ()

@end

@implementation JSDInvestViewController

#pragma mark - 1.Life Cycle

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    //1.設定導航欄
    [self setupNavBar];
    //2.設定view
    [self setupView];
    //3.請求資料
    [self setupData];
    //4.設定通知
    [self setupNotification];
}

#pragma mark - 2.Setting View and Style

- (void)setupNavBar {
     
}

- (void)setupView {

}

#pragma mark - 3.Request Data

- (void)setupData {
   
}

可以到 Xcode 修改 ViewController 檔案模板, 建立的時候即自動生成模板樣式。

#pragma mark - UITableViewDataSource and UITableViewDelegate

#pragma mark - Custom Methods

#pragma mark - Set & Get

#pragma mark - Notification

#pragma mark - Event Response

點語法

在訪問屬性或者修改的過程中,應該全程使用 . 語法來進行。 能提高閱讀性同時, 還能減少程式碼長度。但是又不能濫用。
推薦:

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

反對:

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

私有屬性

要求私有屬性必須在類 extension 下使用 @peoperty 宣告.
例如:

@interface JSDDataService ()

@property (nonatomic, strong) NSDate *lastRefreshTokenTime;
@property (nonatomic, assign) JSDRefreshTokenStatus lastRefreshTokenStatus;
@property (nonatomic, copy) NSString *encodeVersion; //編碼後的App版本號,6位數字,如3.0.1為030001

@end

常量

列舉常量

要求使用 NS_ENUM() 函式而非 enum() 函式進行宣告。 其具有更強的型別檢查和程式碼補全功能。
例如代表網路狀態的常量, 網路連線成功, 失敗,正在連線等。 我們通常採用列舉的方式表示。
例如:

typedef NS_ENUM(NSUInteger, MyEnum) {
    MyEnumValueA = 0,
    MyEnumValueB,
    MyEnumValueC,
};

其他型別常量

多用型別常量, 少用 #define 預處理指令.
推薦:

@implementation 

static const NSTimeInterval KAnimationDuration = 0.3;

NSString *const JSDLoginManagerDidLoginNotification = @"JSDLoginManagerDidLoginNotification";

@interface

extern NSString *const JSDLoginManagerDidLoginNotification;

反對

#define ANIMATION_DURATION 0.3;
#define JSDLoginManagerDidLoginNotification @"JSDLoginManagerDidLoginNotification";

使用型別常量定義在效果一目瞭然, 在使用過程就能確定其常量型別。
需要注意的是如果使用型別常量定義,若常量侷限於某編譯單元,也就是實現檔案裡面,則使用 ==K==作為字首, 若常量在類之外公開出來,則需要使用規定的類名作為字首。

檔案引入方式

  1. 在 .h 檔案中儘量使用 @class 宣告檔案, 直到 .m 檔案中真正需要的時候在使用 @improt 進行引用, 能有效的防止其它類引入此類時, 一併也引入了
  2. @improt 檔案順序: 可以先寫引入系統檔案, 依次到 Public.h 最後才到我們自己編寫的檔案。
  3. 檢查檔案,避免引入到沒有使用到的檔案,發現應及時清除。

多用字面量語法,少用與之等價的語法

我們在建立 NS 開頭的資料型別的時候, 一律採用字面量語法的形式進行建立。
推薦:

NSArray *array = @[@"123", @"456"];
NSDictionary *dictionary = @[@"name": @"jersey", @"age": @"61"];
NSNumber *number = @18;
NSString *string = array[0];

反對:

NSArray *array = [NSArray arrayWithObjects:@"123", @"456", nil];
[NSDictionary dictionaryWithObjectsAndKeys:@"jersey", @"name", @"61", @"age", nil];
NSNumber* number = [NSNumber numberWithInt: 18];
NSString *string = [array objectAtIndex: 1];

功能一致,但是使用字面量能令程式碼更為整潔。並且對於建立陣列,字典使用字面量語法建立,當誤傳 nil 時能更早的發現錯誤。

間距

  • 不要使用 tab 鍵來進行縮排, 應該使用 4 個空格來代替。
  • 方法的大括號和其他的大括號(if/else/switch/while 等等)始終和宣告在同一行開始,在新的一行結束。
  • 方法之間應該正好空一行,這有助於視覺清晰度和程式碼組織性。在方法中的功能塊之間應該使用空白分開,但往往可能應該建立一個新的方法。

推薦:

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

條件語句

  • 條件判斷語句執行主體不管是 if 裡面還是 else 裡面都必須要使用大括號包住, 即使程式碼是一行的情況(Apple SSL/TLS gotofail)。 以及 for 迴圈。
  • if-else中,else不另起一行,與if的反括號在同一行,如上段程式碼所示,注意else前後均有空格。
  • switch-case中,case後的程式碼多餘一行,則需要{}包裹,建議所有case和default後的程式碼塊均用{}包裹。

涉及位置:@interface、方法實現、if-else、switch-case等。
優點: 減少程式碼冗餘行數,程式碼更加緊湊,結構清晰。

推薦:

if(hasSillyName){
    LaughOutLoud()
} else {
    BlowTheHorn();
}

for(int i = 0 ; i < 10 ; i ++){
    BlowTheHorn();
}

反對:

if(hasSillyName)
    LaughOutLoud();               //避免。

for(int i = 0 ; i < 10 ; i ++)
    BlowTheHorn();                //避免。

避免尤達表示式

不要使用尤達表示式。尤達表示式是指,拿一個常量去和變數比較而不是拿變數去和常量比較。它就像是在表達 “藍色是不是天空的顏色” 或者 “高個是不是這個男人的屬性” 而不是 “天空是不是藍的” 或者 “這個男人是不是高個子的”
[圖片上傳失敗...(image-ee9c37-1540171434039)]

表示式條件應該是先寫變數在到常量的判斷過程
推薦:

if ([myValue isEqual:@42]) { 

    } 

不推薦:

if ([@42 isEqual:myValue]) {

    }

黃金大道

在使用條件語句程式設計時,儘管遇到邏輯複雜的程式碼,我們也應該儘量避免其巢狀導致閱讀困難。
儘量使用 return 將不符合邏輯的直接忽略掉. 然後將要執行的程式碼放到判斷語句外面,減少巢狀。

推薦:

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

不推薦:

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

複雜的表示式

當出現複雜的 if 條件的時候, 可以把它們分別提取出來賦值到 BOOL 變數上, 讓邏輯更加清晰明瞭, 約定 2 個條件以上則需要將其單獨提取出來。
例如:

BOOL nameContainsSwift  = [sessionName containsString:@"Swift"];
BOOL isCurrentYear      = [sessionDateCompontents year] == 2014;
BOOL isSwiftSession     = nameContainsSwift && isCurrentYear;

if (isSwiftSession) {
    // Do something very cool
}

三元運算子

推薦使用三元運算子進行運算. 它能使程式碼更加簡潔清晰。
推薦:

result = a > b ? x : y;

不推薦:

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

當三元運算子的第二個引數(if 分支)返回和條件語句中已經檢查的物件一樣的物件的時候,下面的表達方式更靈巧:
推薦:

result = object ? : [self createObject];

不推薦:

result = object ? object : [self createObject];

錯誤處理

很多系統方法通過 error 返回指標的形式來表示錯誤, 我們應該針對其返回值判斷, 而非錯誤變數。
推薦:

NSError *error;
if (![self trySomethingWithError:&error]) {
    // 處理錯誤
}

反對:

NSError *error;
[self trySomethingWithError:&error];
if (error) {
    // 處理錯誤
}

一些蘋果的 API 在成功的情況下會寫一些垃圾值給錯誤引數(如果非空),所以針對錯誤變數可能會造成虛假結果(以及接下來的崩潰)。

NSNotification

約定在我們自己定義 NSNotification 的時候應該把通知的名字定義為一個字串常量, 就像把我們暴露給其他類的字串常量一樣。使用 extern 關鍵字將其在 .h 檔案宣告, 並且在 .m 檔案對其定義。

** 使用約定的 JS || JSD 作為字首, 後跟具體通知名稱, 同時推薦使用 Did/Will 這樣的動詞連線表示最好 **

推薦:

JSDBadgeManager.h
extern NSString *const JSDBadgeInfoDidUpdateNotification;

JSDBadgeManager.m
NSString *const JSDBadgeInfoDidUpdateNotification = @"kJSDBadgeInfoDidUpdateNotification";

Self 迴圈引用

我們在寫 block 回撥的時候, 特別是對於例項方法中呼叫, 很容易導致其迴圈引用。
我們在專案中已經引入一組強弱引用的巨集定義. @weakify(self) 、@strongify(self)
我們在遇到需要修飾 Self 的時候直接使用這組巨集來進行修飾即可。。

例如

[JSDStartInfo getStartInfoSuccess:^{
   @weakify(self)
   [JSDCheckVersion checkWithPass:^{
       @strongify(self);
       [self reloadView];
   }];
}];

MVC模式

專案檔案結構主要以專案每個大模組相應下來進行劃分。如 Main,Home,Invest,Discover,Wealth,More,Account。 這幾個主要功能模組進行一一劃分、
我們想新建新增模組的時候, 必須遵守這個原則去建立資料夾。

code 一致採用 MVC 設計框架, 每個模組功能點一般劃分成 Model,ViewController,View 去進行實現. 每個模組各執其職。

  • Model: 儲存相應的資料結構, 邏輯複雜的情況下, 可以將資料操作邏輯從 ViewController 移動到 Model 去進行實現, 防止 ViewController 程式碼過於冗餘. 不方便閱讀。
  • ViewController: 主要負責獲取 Model 層資料, 進而管理 View 層. 以及 User 一系列互動邏輯, 都由其負責響應管理。 由於此層任務繁重, 邏輯複雜的時候容易造成程式碼冗餘的狀態, 必要的時候我們也可以更換成 MVVC 模式去有效的避免 MVC 這個缺點。
  • View: 主要負責接收 Model 層資料, 進行頁面渲染 資料展示, 一般無特殊情況, 建議使用 Xib 去完成介面構建, 除非是特殊頁面, 否則儘量統一使用 Xib 的形式 去完成每個介面。

圖片管理

專案中的圖片一致存放到 Assets.xcassets 進行管理. 從 V3.0 版本開始是按模組劃分進行管理的, 但是到後面版本的更新迭代中, 出現了按版本順序進行分組, 建議後面將圖片一致儲存到原有的模組分組中去,然後每次不需要使用的圖片,及時清除, 減少 App 體積。

** 圖片名稱: 圖片名稱可以以模組作為首接 _ 連線其相應功能點。這樣在以後查詢起來也相對方便 **
如: trade_result_fail 投資,結果頁,狀態。 discover_activity 發現,活動

Xcode 版本.

Xcode 版本更新迭代很快, 建議定時到 App Store 進行更新到最新版。

三方庫管理工具

使用 Cocoapods 統一對第三庫進行管理, 建議定時更新到最新版本, 保持版本一致。
關於引用三方庫原則: github 上的專案必須要求 200 Star 以上

TODO

當在開發某個功能中遇到一些疑難雜症的問題, 但是又由於時間緊急, 來不及把細節做到完美, 如果在不影響當前功能使用的情況下, 為了防止在後面的開發中遺忘掉, 我們必須要在相應的地方添加註釋 然後以 ==TODO:== 的格式, 接相關問題描述新增進來, 待後面有空閒時間之後回來進行修復完善。

CGRect 使用

當需要訪問 CGRect 中某個成員時, 應該使用 CGGeometry 函式來直接訪問。 而不是使用 .語法來獲取。

推薦:

CGRect frame = self.view.frame;

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

反對

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;

使用 Route 管理控制器切換

為了方便業務擴充套件,以及更好的管理 ViewController,對 JLRoutes 三方庫進行一層封裝來實現,對 控制器的管理。 所以專案中涉及到的 Push Presen Dismiss 等, 一致使用封裝的 JSDRouter 來完成。
一行程式碼即可完成控制器的跳轉
例如:

 [JSDRouter openURL:JSDRouteAccountCenter];
 [JSDRouter openURL:JSDRouteTradeExtract info:@{@"withdrawInfo":withdrawInfo}];
 [JSDRouter openURL:JSDROUTE_BACK];

防止重複造輪子

在著手開發新需求之前,應先觀察一下專案中,是否有已經封裝好的相關功能類,如果已經有了,最好在當前類上新增 Catogory 進行擴充套件。

專案中的基礎功能類, 以及對應封裝的工具一致儲存在 Class ---> Public。

單例

要求必須使用執行緒安全的 GCD 函式進行建立。

例如:

+ (instancetype)shareInstance {
    static id sharedInstance = nil;
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}

關於縮寫

Objective-C 本身就是讓閱讀者讀起來就像在閱讀句子一樣的一門語言, 變數, 常量 特別是方法名相對其他語言來說會相對長一點,主要用意就是為了表達更加清晰明瞭, 儘量不讓閱讀的人產生歧義,並且基本能達到光看程式碼名字就知道其用意。
所以我們也應該嚴格遵守語言的設計風格, 和大家保持一致, 能有效的提高我們協同開發的效率同時,也能使我們能編寫出更容易讓大家接受的程式碼。

主要參考文件: Cocoa guide 可接受的縮略語和首字母縮略詞

不推薦大家使用縮寫,寧願命名長一點,也一定要保證語義表達清晰的原則。

Abbreviation Meaning and comments
alloc Allocate
alt Alternate.
app Application. For example, NSApp the global application object. However,“application” is spelled out in delegate methods, notifications, and so on.
calc Calculate.
dealloc Deallocate.
func Function.
horiz Horizontal.
info Information.
init Initialize (for methods that initialize new objects).
int Integer (in the context of a C int—for an NSInteger value, use integer).
max Maximum.
min Minimum.
msg Message.
nib Interface Builder archive.
pboard Pasteboard (but only in constants).
rect Rectangle.
Rep Representation (used in class name such as NSBitmapImageRep).
temp Temporary.
vert Vertical.

主要參考資料:

Coding Guidelines for Cocoa
Google Objective-C Style Guide
紐約時報 移動團隊 Objective-C 規範指南
禪與 Objective-C 程式設計藝術

最後

希望此篇文章對您有所幫助,如有不對的地方,希望大家能留言指出糾正。
謝謝!!!!!
學習的路上,與君共勉!!!

本文原創作者:Jersey. 歡迎轉載,請註明出處和本文連結



作者:澤西島上咖啡
連結:https://www.jianshu.com/p/941389de03d6
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。