iOS Coding Style Guide 代碼規範
[TOC]
### 前言
代碼規範可以說是老生常談的話題了, 也是程序員的自我修養, 雖然一套好的代碼規範不能使程序運行的更加流暢, 不能使程序直接的影響到程序的功能執行,但是如果能再發開之前就能明確定義一套代碼規範,並且嚴格的去執行,肯定能非常有效的提高代碼閱讀性,高的閱讀性也使得後期開發,維護等事半功倍,上手難度降低,在新人加入進行也能更快的融入團隊。
下面我分別按幾個要素概括列舉一下自己團隊制定的一套代碼規範,提供大家參考。
###命名規範
我們儘可能遵守 Apple 的命名約定, 其推薦使用長的,描述性強的方法和變數名,使其閱讀起來更加清晰易懂。不能隨意使用縮寫,導致其他人員閱讀代碼困難。 [The coding guide for cocoa](Code Naming Basics)
####駝峰命名
針對屬性,變數,方法等均採用小寫字母開頭的駝峰命名準則。
####前綴
項目名稱,類名,文件名都應該保持一致的前綴名,根據 **Apple Guide** 建議類名前綴應該使用 2 個英文以上最好,因為 Apple 寫的框架都是直接使用 2 個英文字母開頭, 使用 3 個字母 能有效防止類名重複影響工程。
但是由於項目歷史原因一直採用的是 ==YM== 作為前綴, 所以暫時先對這塊不進行改動。 後面如果有新項目的話, 一致採用 ==YEA== 開頭作為前綴。
####方法命名
根據 Cocoa 命名方法規則,我們應該准守這幾個點。
1. 使用小寫字母開頭,後面嵌套連接的字母使用大寫開頭。
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)YM_methodName
- (void)YEA_methodName ```
###統一的ViewController 模板
針對大多數沒有特別複雜邏輯的控制器實現文件, 我們制定了一套 @implementation 實現模板.
如下:
```
#import "YMInvestDetailsVC.h"
@interface YMInvestDetailsVC ()
@end
@implementation YMInvestDetailsVC
#pragma mark - Coming Soon 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 YMDataService ()
@property (nonatomic, strong) NSDate *lastRefreshTokenTime;
@property (nonatomic, assign) YMRefreshTokenStatus 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 YMLoginManagerDidLoginNotification = @"YMLoginManagerDidLoginNotification";
@interface
extern NSString *const YMLoginManagerDidLoginNotification;
```
反對
```
#define ANIMATION_DURATION 0.3;
#define YMLoginManagerDidLoginNotification @"YMLoginManagerDidLoginNotification";
```
使用類型常量定義在效果一目了然, 在使用過程就能確定其常量類型。
需要注意的是如果使用類型常量定義,若常量局限於某**編譯單元**,也就是實現文件裡面,則使用 ==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-655644-1540127309126)]
**表達式條件應該是先寫變數在到常量的判斷過程**
推薦:
```
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 文件對其定義。
** 使用約定的 YM || YEA 作為前綴, 後跟具體通知名稱, 同時推薦使用 Did/Will 這樣的動詞連接表示最好 **
推薦:
```
YMBadgeManager.h
extern NSString *const YMBadgeInfoDidUpdateNotification;
YMBadgeManager.m
NSString *const YMBadgeInfoDidUpdateNotification = @"kYMBadgeInfoDidUpdateNotification";
```
###Self 循環引用
我們在寫 block 回調的時候, 特別是對於實例方法中調用, 很容易導致其循環引用。
我們在項目中已經引入一組強弱引用的宏定義. @weakify(self) 、@strongify(self)
我們在遇到需要修飾 Self 的時候直接使用這組宏來進行修飾即可。。
例如
```
[YMStartInfo getStartInfoSuccess:^{
@weakify(self)
[YMCheckVersion 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 等, 一致使用封裝的 YMRouter 來完成。
**一行代碼即可完成控制器的跳轉**
例如:
```
[YMRouter openURL:YMRouteAccountCenter];
[YMRouter openURL:YMRouteTradeExtract info:@{@"withdrawInfo":withdrawInfo}];
[YMRouter openURL:YMROUTE_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 可接受的縮略語和首字母縮略詞](Acceptable Abbreviations and Acronyms)
不推薦大家使用縮寫,寧願命名長一點,也一定要保證語義表達清晰的原則。
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](Introduction to Coding Guidelines for Cocoa)
[Google Objective-C Style Guide](google/styleguide)
[紐約時報 移動團隊 Objective-C 規範指南](NYTimes/objective-c-style-guide)
[禪與 Objective-C 編程藝術](oa414/objc-zen-book-cn)
## 最後
>希望此篇文章對您有所幫助,如有不對的地方,希望大家能留言指出糾正。
>謝謝!!!!!
>學習的路上,與君共勉!!!
>>本文原創作者:[Jersey](澤西島上咖啡 - 簡書). 歡迎轉載,請註明出處和[本文鏈接](pod install error , 項目包含框架衝突)
推薦閱讀: