前 言
- 需求是暫時的,只有變化才是永恆的,面向變化程式設計,而不是面向需求程式設計。
- 不要過分追求技巧,降低程式的可讀性。
- 簡潔的程式碼可以讓bug無處藏身。要寫出明顯沒有bug的程式碼,而不是沒有明顯bug的程式碼。
- 先把眼前的問題解決掉,解決好,再考慮將來的擴充套件問題。
一、命名規範
1、統一要求
含義清楚,儘量做到不需要註釋也能瞭解其作用,若做不到,就加註釋,使用全稱,不使用縮寫。
2、類名
大駝峰式命名:每個單詞的首字母都採用大寫字母
==例:== MFHomePageViewController
3、私有變數
- 私有變數放在 .m 檔案中宣告
- 以 _ 開頭,第一個單詞首字母小寫,後面的單詞的首字母全部大寫。
==例:== NSString *_somePrivateVariable
4、property變數
- 小駝峰式命名:第一個單詞以小寫字母開始,後面的單詞的首字母全部大寫
- 屬性的關鍵字推薦按照 原子性,讀寫,記憶體管理的順序排列。
Block
、NSString
屬性應該使用copy
關鍵字- 禁止使用
synthesize
關鍵詞
==例:==
1 2 3 4 5 6 |
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
宣告,在實現檔案中定義其值
- 對於侷限於某編譯單元(實現檔案)的常量,以字元k開頭,例如
==例:==
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//巨集定義的常量 #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
定義位移列舉
==例:==
1 2 3 4 5 6 7 8 9 10 11 |
typedef NS_ENUM(NSInteger, UIViewAnimationTransition) { UIViewAnimationTransitionNone, UIViewAnimationTransitionFlipFromLeft, UIViewAnimationTransitionFlipFromRight, UIViewAnimationTransitionCurlUp, UIViewAnimationTransitionCurlDown, }; typedef NS_OPTIONS(NSUInteger, UIControlState) { UIControlStateNormal = 0, UIControlStateHighlighted = 1 |
7、Delegate
- 用
delegate
做字尾,如 - 用
optional
修飾可以不實現的方法,用required
修飾必須實現的方法 - 當你的委託的方法過多, 可以拆分資料部分和其他邏輯部分, 資料部分用
dataSource
做字尾. 如 - 使用
did
和will
通知Delegate
已經發生的變化或將要發生的變化。 - 類的例項必須為回撥方法的引數之一
- 回撥方法的引數只有類自己的情況,方法名要符合實際含義
- 回撥方法存在兩個以上引數的情況,以類的名字開頭,以表明此方法是屬於哪個類的
==例:==
1 2 3 4 5 6 7 8 9 10 11 |
@protocol UITableViewDataSource @required //回撥方法存在兩個以上引數 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section; @optional //回撥方法的引數只有類自己 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView; // Default is 1 if not implemented |
1 2 3 4 5 6 7 |
@protocol UITableViewDelegate @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
等)進一步說明屬性意思,但不要使用do
或does
,因為這些助動詞沒什麼實際意義。也不要在動詞前使用副詞或形容詞修飾
==例:==
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
//不要使用 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
語句,就對這些語句進行分組,每個分組的註釋是可選的。
1 2 3 4 5 6 7 8 9 |
// Frameworks #import ; // Models #import "NYTUser.h" // Views #import "NYTButton.h" #import "NYTUserView.h" |
2、屬性註釋
寫在屬性之後,用兩個空格隔開
==例:==
1 |
@property (nonatomic, readwrite, strong) UIView *headerView; //註釋 |
3、方法宣告註釋:
一個函式(方法)必須有一個字串文件來解釋,除非它:
- 非公開,私有函式。
- 很短。
- 顯而易見。
而其餘的,包括公開介面,重要的方法,分類,以及協議,都應該伴隨文件(註釋):
- 以/開始
- 第二行是總結性的語句
- 第三行永遠是空行
- 在與第二行開頭對齊的位置寫剩下的註釋。
建議這樣寫:
1 2 3 4 5 |
/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+/
==例:==
1 2 3 4 5 6 7 8 9 10 11 |
/** @param tableView @param section <a href='http://www.jobbole.com/members/wx1409399284'>@return</a> */ - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { //... } |
4、程式碼塊註釋
單行的用//+空格
開頭,多行的採用/* */
註釋
5、TODO
使用//TODO:說明
標記一些未完成的或完成的不盡如人意的地方
==例:==
1 2 3 4 5 |
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { //TODO:增加初始化 return YES; } |
三、程式碼格式化規範
1、指標*
位置
定義一個物件時,指標*
靠近變數
==例:== NSString *userName;
2、方法的宣告和定義
在 - 、+
和 返回值之間留一個空格,方法名和第一個引數之間不留空格
==例:==
1 |
- (void)insertSubview:(UIView *)view atIndex:(NSInteger)index; |
3、程式碼縮排
- 不要在工程裡使用 Tab 鍵,使用空格來進行縮排。在 Xcode > Preferences > Text Editing 將 Tab 和自動縮排都設定為 4 個空格
Method
與Method
之間空一行- 一元運算子與變數之間沒有空格、二元運算子與變數之間必須有空格
==例:==
1 2 3 4 5 6 |
!bValue fLength = fWidth * 2; - (void)sampleMethod1; - (void)sampleMethod2; |
4、對method進行分組
使用#pragma mark -
對method
進行分組
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
#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等)
: 左括號跟在第一行後邊
==例:==
1 2 3 4 5 6 7 |
- (void)sampleMethod { BOOL someCondition = YES; if(someCondition) { // do something here } } |
6、property變數
==例:==
1 |
@property (nonatomic, readwrite, strong) UIView *headerView; //註釋 |
四、編碼規範
1、if語句
①、須列出所有分支(窮舉所有的情況),而且每個分支都須給出明確的結果。
==推薦這樣寫:==
1 2 |
var hintStr; if (count |
==不推薦這樣寫:==
1 2 |
var hintStr; if (count |
②、不要使用過多的分支,要善於使用return來提前返回錯誤的情況,把最正確的情況放到最後返回。
==推薦這樣寫:==
1 2 3 4 5 |
if (!user.UserName) return NO; if (!user.Password) return NO; if (!user.Email) return NO; return YES; |
==不推薦這樣寫:==
1 2 3 4 5 6 7 8 9 |
BOOL isValid = NO; if (user.UserName) { if (user.Password) { if (user.Email) isValid = YES; } } return isValid; |
③、條件過多,過長的時候應該換行。條件表示式如果很長,則需要將他們提取出來賦給一個BOOL值,或者抽取出一個方法
==推薦這樣寫:==
1 2 3 4 5 6 |
if (condition1 && condition2 && condition3 && condition4) { // Do something } |
1 2 3 4 |
BOOL finalCondition = condition1 && condition2 && condition3 && condition4 if (finalCondition) { // Do something } |
1 2 3 4 5 6 7 8 9 10 11 |
if ([self canDelete]){ // Do something } - (BOOL)canDelete { BOOL finalCondition1 = condition1 && condition2 BOOL finalCondition2 = condition3 && condition4 return condition1 && condition2; } |
==不推薦這樣寫:==
1 2 3 |
if (condition1 && condition2 && condition3 && condition4) { // Do something } |
④、條件語句的判斷應該是變數在右,常量在左。
==推薦:==
1 2 3 4 5 6 7 8 |
if (6 == count) { } if (nil == object) { } if (!object) { } |
==不推薦:==
1 2 3 4 5 |
if (count == 6) { } if (object == nil) { } |
if (object == nil)
容易誤寫成賦值語句,if (!object)
寫法很簡潔
⑤、每個分支的實現程式碼都須被大括號包圍
==推薦:==
1 2 3 |
if (!error) { return success; } |
==不推薦:==
1 2 |
if (!error) return success; |
可以如下這樣寫:
1 |
if (!error) return success; |
2、for語句
①、不可在for迴圈內修改迴圈變數,防止for迴圈失去控制。
1 |
for (int index = 0; index |
②、避免使用continue和break。
continue
和break
所描述的是“什麼時候不做什麼”,所以為了讀懂二者所在的程式碼,我們需要在頭腦裡將他們取反。
其實最好不要讓這兩個東西出現,因為我們的程式碼只要體現出“什麼時候做什麼”就好了,而且通過適當的方法,是可以將這兩個東西消滅掉的:
- 如果出現了continue,只需要把continue的條件取反即可
1 2 3 4 5 6 7 |
var filteredProducts = Array() for level in products { if level.hasPrefix("bad") { continue } filteredProducts.append(level) } |
我們可以看到,通過判斷字串裡是否含有“bad”
這個prefix
來過濾掉一些值。其實我們是可以通過取反,來避免使用continue
的:
1 2 3 4 5 |
for level in products { if !level.hasPrefix("bad") { filteredProducts.append(level) } } |
- 消除
while
裡的break:
將break
的條件取反,併合併到主迴圈裡
在while
裡的break
其實就相當於“不存在”,既然是不存在的東西就完全可以在最開始的條件語句中將其排除。
while
裡的break:
1 2 3 4 5 6 |
while (condition1) { ... if (condition2) { break; } } |
取反併合併到主條件:
1 2 3 |
while (condition1 && !condition2) { ... } |
- 在有返回值的方法裡消除
break:
將break
轉換為return
立即返回
有人喜歡這樣做:在有返回值的方法裡break之後,再返回某個值。其實完全可以在break的那一行直接返回。
1 2 3 4 5 6 7 8 9 10 11 |
func hasBadProductIn(products: Array) -> Bool { var result = false for level in products { if level.hasPrefix("bad") { result = true break } } return result } |
遇到錯誤條件直接返回:
1 2 3 4 5 6 7 8 |
func hasBadProductIn(products: Array) -> Bool { for level in products { if level.hasPrefix("bad") { return true } } return false } |
這樣寫的話不用特意宣告一個變數來特意儲存需要返回的值,看起來非常簡潔,可讀性高。
3、Switch語句
①、每個分支都必須用大括號括起來
推薦這樣寫:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
switch (integer) { case 1: { // ... } break; case 2: { // ... break; } default:{ // ... break; } } |
②、使用列舉型別時,不能有default
分支, 除了使用列舉型別以外,都必須有default
分支
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
RWTLeftMenuTopItemType menuType = RWTLeftMenuTopItemMain; switch (menuType) { case RWTLeftMenuTopItemMain: { // ... break; } case RWTLeftMenuTopItemShows: { // ... break; } case RWTLeftMenuTopItemSchedule: { // ... break; } } |
在Switch
語句使用列舉型別的時候,如果使用了default
分支,在將來就無法通過編譯器來檢查新增的列舉型別了。
4、函式
①、一個函式只做一件事(單一原則)
每個函式的職責都應該劃分的很明確(就像類一樣)。
==推薦:==
1 2 |
dataConfiguration() viewConfiguration() |
==不推薦:==
1 2 3 4 5 |
void dataConfiguration() { ... viewConfiguration() } |
②、對於有返回值的函式(方法),每一個分支都必須有返回值
==推薦:==
1 2 3 4 5 6 7 8 9 10 |
int function() { if(condition1){ return count1 }else if(condition2){ return count2 }else{ return defaultCount } } |
==不推薦:==
1 2 3 4 5 6 7 8 |
int function() { if(condition1){ return count1 }else if(condition2){ return count2 } } |
③、對輸入引數的正確性和有效性進行檢查,引數錯誤立即返回
==推薦:==
1 2 3 4 5 6 7 8 9 10 11 12 |
void function(param1,param2) { if(param1 is unavailable){ return; } if(param2 is unavailable){ return; } //Do some right thing } |
④、如果在不同的函式內部有相同的功能,應該把相同的功能抽取出來單獨作為另一個函式
原來的呼叫:
1 2 3 4 5 6 7 8 9 |
void logic() { a(); b(); if (logic1 condition) { c(); } else { d(); } } |
將a,b函式抽取出來作為單獨的函式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
void basicConfig() { a(); b(); } void logic1() { basicConfig(); c(); } void logic2() { basicConfig(); d(); } |
⑤、將函式內部比較複雜的邏輯提取出來作為單獨的函式
一個函式內的不清晰(邏輯判斷比較多,行數較多)的那片程式碼,往往可以被提取出去,構成一個新的函式,然後在原來的地方呼叫它這樣你就可以使用有意義的函式名來代替註釋,增加程式的可讀性。
舉一個傳送郵件的例子:
1 2 3 4 5 6 7 8 9 |
openEmailSite(); login(); writeTitle(title); writeContent(content); writeReceiver(receiver); addAttachment(attachment); send(); |
中間的部分稍微長一些,我們可以將它們提取出來:
1 2 3 4 5 6 7 |
void writeEmail(title, content,receiver,attachment) { writeTitle(title); writeContent(content); writeReceiver(receiver); addAttachment(attachment); } |
然後再看一下原來的程式碼:
1 2 3 4 |
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