最近重構項目組件,看到項目中存在一些命名和方法分塊方面存在一些問題,結合平時經驗和 Apple官方代碼規範 在此整理出 iOS 工程規範。提出第一個版本,如果後期覺得有不完善的地方,繼續提出來不斷完善,文檔在此記錄的目的就是爲了大家的代碼可讀性較好,後來的人或者團隊裏面的其他人看到代碼可以不會因爲代碼風格和可讀性上面造成較大時間的開銷。
軟件的生命週期貫穿產品的開發,測試,生產,用戶使用,版本升級和後期維護等過程,只有易讀,易維護的軟件代碼才具有生命力。
一些原則
- 長的,描述性的方法和變量命名是好的。不要使用簡寫,除非是一些大家都知道的場景比如 VIP。不要使用 bgView,推薦使用 backgroundView
- 見名知意。含義清楚,做好不加註釋代碼自我表述能力強。(前提是代碼足夠規範)
- 不要過分追求技巧,降低代碼可讀性
- 刪除沒必要的代碼。比如我們新建一個控制器,裏面會有一些不會用到的代碼,或者註釋起來的代碼,如果這些代碼不需要,那就刪除它,留着偷懶嗎?下次需要自己手寫
- 在方法內部不要重複計算某個值,適當的情況下可以將計算結果緩存起來
- 儘量減少單例的使用。
- 提供一個統一的數據管理入口,不管是 MVC、MVVM、MVP 模塊內提供一個統一的數據管理入口會使得代碼變得更容易管理和維護。
- 除了 .m 文件中方法,其他的地方"{"不需要另起一行。
- (void)getGooodsList
{
// ...
}
- (void)doHomework
{
if (self.hungry) {
return;
}
if (self.thirsty) {
return;
}
if (self.tired) {
return;
}
papapa.then.over;
}
變量
- 一個變量最好只有一個作用,切勿爲了節省代碼行數,覺得一個變量可以做多個用途。(單一原則)
- 方法內部如果有局部變量,那麼局部變量應該靠近在使用的地方,而不是全部在頂部聲明全部的局部變量。
運算符
- 1元運算符和變量之間不需要空格。例如:++n
- 2元運算符與變量之間需要空格隔開。例如: containerWidth = 0.3 * Screen_Width
- 當有多個運算符的時候需要使用括號來明確正確的順序,可讀性較好。例如: 2 << (1 + 2 * 3 - 4)
條件表達式
- 當有條件過多、過長的時候需要換行,爲了代碼看起來整齊些
//good
if (condition1() &&
condition2() &&
condition3() &&
condition4()) {
// Do something
}
//bad
if (condition1() && condition2() && condition3() && condition4()) { // Do something }
- 在一個代碼塊裏面有個可能的情況時善於使用
return
來結束異常的情況。
- (void)doHomework
{
if (self.hungry) {
return;
}
if (self.thirsty) {
return;
}
if (self.tired) {
return;
}
papapa.then.over;
}
- 每個分支的實現都必須使用 {} 包含。
// bad
if (self.hungry) self.eat()
// good
if (self.hungry) {
self.eat()
}
- 條件判斷的時候應該是變量在左,條件在右。 if ( currentCursor == 2 ) { //… }
- switch 語句後面的每個分支都需要用大括號括起來。
- switch 語句後面的 default 分支必須存在,除非是在對枚舉進行 switch。
switch (menuType) {
case menuTypeLeft: {
// ...
break;
}
case menuTypeRight: {
// ...
break;
}
case menuTypeTop: {
// ...
break;
}
case menuTypeBottom: {
// ...
break;
}
}
類名
- 大寫駝峯式命名。每個單詞首字母大寫。比如「申請記錄控制器」ApplyRecordsViewController
- 每個類型的命名以該類型結尾。
- ViewController:使用
ViewController
結尾。例子:ApplyRecordsViewController - View:使用
View
結尾。例子:分界線:boundaryView - NSArray:使用
s
結尾。比如商品分類數據源。categories - UITableViewCell:使用
Cell
結尾。比如 MyProfileCell - Protocol:使用
Delegate
或者Datasource
結尾。比如 XQScanViewDelegate - Tool:工具類
- 代理類:Delegate
- Service 類:Service
- ViewController:使用
類的註釋
有時候我們需要爲我們創建的類設置一些註釋。我們可以在類的下面添加。
枚舉
枚舉的命名和類的命名相近。
typedef NS_ENUM(NSInteger, UIControlContentVerticalAlignment) {
UIControlContentVerticalAlignmentCenter = 0,
UIControlContentVerticalAlignmentTop = 1,
UIControlContentVerticalAlignmentBottom = 2,
UIControlContentVerticalAlignmentFill = 3,
};
宏
- 全部大寫,單詞與單詞之間用
_
連接。 - 以
K
開頭。後面遵循大寫駝峯命名。「不帶參數」
#define HOME_PAGE_DID_SCROLL @"com.xq.home.page.tableview.did.scroll"
#define KHomePageDidScroll @"com.xq.home.page.tableview.did.scroll"
屬性
書寫規則,基本上就是 @property 之後空一格,括號,裏面的 線程修飾詞、內存修飾詞、讀寫修飾詞,空一格 類 對象名稱
根據不同的場景選擇合適的修飾符。
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, assign, readonly) BOOL loading;
@property (nonatomic, weak) id<#delegate#> delegate;
@property (nonatomic, copy) <#returnType#> (^<#Block#>)(<#parType#>);
單例
單例適合全局管理狀態或者事件的場景。一旦創建,對象的指針保存在靜態區,單例對象在堆內存中分配的內存空間只有程序銷燬的時候纔會釋放。基於這種特點,那麼我們類似 UIApplication 對象,需要全局訪問唯一一個對象的情況才適合單例,或者訪問頻次較高的情況。我們的功能模塊的生命週期肯定小於 App 的生命週期,如果多個單例對象的話,勢必 App 的開銷會很大,糟糕的情況系統會殺死 App。如果覺得非要用單例比較好,那麼注意需要在合適的場合 tearDown 掉。
單例的使用場景概括如下:
- 控制資源的使用,通過線程同步來控制資源的併發訪問。
- 控制實例的產生,以達到節約資源的目的。
- 控制數據的共享,在不建立直接關聯的條件下,讓多個不相關的進程或線程之間實現通信。
+ (instancetype)sharedInstance
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//because has rewrited allocWithZone use NULL avoid endless loop lol.
_sharedInstance = [[super allocWithZone:NULL] init];
});
return _sharedInstance;
}
+ (id)allocWithZone:(struct _NSZone *)zone
{
return [TestNSObject sharedInstance];
}
+ (instancetype)alloc
{
return [TestNSObject sharedInstance];
}
- (id)copy
{
return self;
}
- (id)mutableCopy
{
return self;
}
- (id)copyWithZone:(struct _NSZone *)zone
{
return self;
}
私有變量
推薦以 _
開頭,寫在 .m 文件中。例如 NSString * _somePrivateVariable
代理方法
- 類的實例必須作爲方法的參數之一。
- 對於一些連續的狀態的,可以加一些 will(將要)、did(已經)
- 以類的名稱開頭
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;
方法
- 方法與方法之間間隔一行
- 大量的方法儘量要以組的形式放在一起,比如生命週期函數、公有方法、私有方法、setter && getter、代理方法…
- 方法最後面的括號需要另起一行。遵循 Apple 的規範
- 對於其他場景的括號,括號不需要單獨換行。比如 if 後面的括號。
- 如果方法參數過多過長,建議多行書寫。用冒號進行對齊。
- 一個方法內的代碼最好保持在50行以內,一般經驗來看如果一個方法裏面的代碼行數過多,代碼的閱讀體驗就很差(別問爲什麼,做過重構代碼行數很長的人都有類似的心情)
- 一個函數只做一個事情,做到單一原則。所有的類、方法設計好後就可以類似搭積木一樣實現一個系統。
- 對於有返回值的函數,且函數內有分支情況。確保每個分支都有返回值。
- 函數如果有多個參數,外部傳入的參數需要檢驗參數的非空、數據類型的合法性,參數錯誤做一些措施:立即返回、斷言。
- 多個函數如果有邏輯重複的代碼,建議將重複的部分抽取出來,成爲獨立的函數進行調用
- (instancetype)init
{
self = [super init];
if (self) {
<#statements#>
}
return self;
}
- (void)doHomework:(NSString *)name
period:(NSInteger)second
score:(NSInteger)score;
- 方法如果有多個參數的情況下需要注意是否需要介詞和連詞。很多時候在不知道如何抉擇測時候思考下蘋果的一些 API 的方法命名。
//good
- (instancetype)initWithAge:(NSInteger)age name:(NSString *)name;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
//bad
- (instancetype)initWithAge:(NSInteger)age andName:(NSString *)name;
- (void)tableView:(UITableView *)tableView :(NSIndexPath *)indexPath;
.m
文件中的私有方法需要在頂部進行聲明- 方法組之間也有個順序問題。
- 在文件最頂部實現屬性的聲明、私有方法的聲明(很多人省去這一步,問題不大,但是蠻多第三方的庫都寫了,看起來還是會很方便,建議書寫)。
- 在生命週期的方法裏面,比如 viewDidLoad 裏面只做界面的添加,而不是做界面的初始化,所有的 view 初始化建議放在 getter 裏面去做。往往 view 的初始化的代碼長度會比較長、且一般會有多個 view 所以 getter 和 setter 一般建議放在最下面,這樣子頂部就可以很清楚的看到代碼的主要邏輯。
- 所有button、gestureRecognizer 的響應事件都放在這個區域裏面,不要到處亂放。
文件基本上就是
//___FILEHEADER___
#import "___FILEBASENAME___.h"
/*ViewController*/
/*View&&Util*/
/*model*/
/*NetWork InterFace*/
/*Vender*/
@interface ___FILEBASENAMEASIDENTIFIER___ ()
@end
@implementation ___FILEBASENAMEASIDENTIFIER___
#pragma mark - life cycle
- (void)viewWillAppear:(BOOL)animated
{
[super viewDidAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = <#value#>;
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewDidAppear:animated];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidAppear:animated];
}
#ifdef DEBUG
- (void)dealloc
{
NSLog(@"%s",__func__);
}
#endif
#pragma mark - public Method
#pragma mark - private method
#pragma mark - event response
#pragma mark - UITableViewDelegate
#pragma mark - UITableViewDataSource
//...(多個代理方法依次往下寫)
#pragma mark - getters and setters
@end
圖片資源
- 單個文件的命名
文件資源的命名也需要一定的規範,形式爲:功能模塊名_類別_功能_狀態@nx.png
[email protected]、[email protected]
[email protected]、[email protected] - 資源的文件夾命名
最好也參考 App 按照功能模塊建立對應的實體文件夾目錄,最後到對應的目錄下添加相應的資源文件。
註釋
- 對於類的註釋寫在當前類文件的頂部
- 對於屬性的註釋需要寫在屬性後面的地方。 //**<userId*/
- 對於 .h 文件中方法的註釋,一律按快捷鍵
command+option+/
。三個快捷鍵解決。按需在旁邊對方法進行說明解釋、返回值、參數的說明和解釋 - 對於 .m 文件中的方法的註釋,在方法的旁邊添加
//
。 - 註釋符和註釋內容需要間隔一個空格。 例如: // fetch goods list
版本規範
採用 A.B.C 三位數字命名,比如:1.0.2,當有更新的情況下按照下面的依據
版本號 | 右說明對齊標題 | 示例 |
---|---|---|
A.b.c | 屬於重大內容的更新 | 1.0.2 -> 2.0.0 |
a.B.c | 屬於小部分內容的更新 | 1.0.2 -> 1.1.1 |
a.b.C | 屬於補丁更新 | 1.0.2 -> 1.0.3 |
改進
我們知道了平時在使用 Xcode 開發的過程中使用的系統提供的代碼塊所在的地址和新建控制器、模型、view等的文件模版的存放文件夾地址後,我們就可以設想下我們是否可以定製自己團隊風格的控制器模版、是否可以打造和維護自己團隊的高頻使用的代碼塊?
答案是可以的。
Xcode 代碼塊的存放地址:~/Library/Developer/Xcode/UserData/CodeSnippets
Xcode 文件模版的存放地址:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Templates/File Templates/
意義
- 爲了個人或者團隊開發者的代碼更加規範。Property的書寫的時候的空格、線程修飾詞、內存修飾詞的先後順序
- 提供大量可用的代碼塊,提高開發效率。比如在 Xcode 裏面敲 UITableView_init 便可以自動懶加載創建一個 UITabelView 對象,你只需要設置在指定的位置寫相應的參數
- 通過一些代碼塊提高代碼規範、避免一些bug。比如曾看到過 block 屬性用 strong 修飾的代碼,造成內存泄漏。舉個例子你在 Xcode 中輸入 Property_delegate 就會出來
@property (nonatomic, weak) id<<#delegate#>> delegate;
,你輸入 Property_block 就會出來@property (nonatomic, copy) <#returnType#> (^<#Block#>)(<#parType#>);
代碼塊的改造
我們可以將屬性、控制器生命週期方法、單例構造一個對象的方法、代理方法、block、GCD、UITableView 懶加載、UITableViewCell 註冊、UITableView 代理方法的實現、UICollectionVIew 懶加載、UICollectionVIewCell 註冊、UICollectionView 的代理方法實現等等組織爲 codesnippets
思考
-
封裝好 codesnippets 之後團隊除了你編寫這個項目的人如何使用?如何知道是否有這個代碼塊?
方案:先在團隊內召開代碼規範會議,大家都統一知道這個事情在。之後大家共同維護 codesnippets。用法見下
屬性:通過 Property_類型 開頭,回車鍵自動補全。比如 Strong 類型,編寫代碼通過 Property_Strong 回車鍵自動補全成如下格式
@property (nonatomic, strong) <#Class#> *<#object#>;
方法:以 Method_關鍵詞 回車鍵確認,自動補全。比如 Method_UIScrollViewDelegate 回車鍵自動補全成 如下格式
#pragma mark - UIScrollViewDelegate - (void)scrollViewDidScroll:(UIScrollView *)scrollView { }
各種常見的 Mark:以 Mark_關鍵詞 回車確認,自動補全。比如 Method_MethodsGroup 回車鍵自動補全成 如下格式
#pragma mark - life cycle #pragma mark - public Method #pragma mark - private method #pragma mark - event response #pragma mark - UITableViewDelegate #pragma mark - UITableViewDataSource #pragma mark - getters and setters
-
封裝好 codesnippets 之後團隊內如何統一?想到一個方案,可以將團隊內的 codesnippets 共享到 git,團隊內的其他成員再從雲端拉取同步。這樣的話團隊內的每個成員都可以使用最新的 codesnippets 來編碼。
編寫 shell 腳本。幾個關鍵步驟:
- 給系統文件夾授權
- 在腳本所在文件夾新建存放代碼塊的文件夾
- 將系統文件夾下面的代碼塊複製到步驟2創建的文件夾下面
- 將當前的所有文件提交到 Git 倉庫
文件模版的改造
我們觀察系統文件模版的特點,和在 Xcode 新建文件模版對應。
所以我們新建 Custom 文件夾,將系統 Source 文件夾下面的 Cocoa Touch Class.xctemplate 複製到 Custom 文件夾下。重命名爲我們需要的名字,我這裏以“Power”爲例
進入 PowerViewController.xctemplate/PowerViewControllerObjective-C
修改 ___FILEBASENAME___.h
和 ___FILEBASENAME___.m
文件內容
在替換 .h 文件內容的時候後面改爲 UIViewController,不然其他開發者新建文件模版的時候出現的不是 UIViewController 而是我們的 PowerViewController
修改 TemplateInfo.plist
思考:
-
如何使用
商量好一個標識(“Power”)。比如我新建了單例、控制器、Model、UIView、UITableViewCell、UICollectionViewCell6個模版,都以爲 Power 開頭。
-
如何共享
以 shell 腳本爲工具。使用腳本將 git 雲端的代碼模版同步到本地 Xcode 文件夾對應的位置就可以使用了。關鍵步驟:
- git clone 代碼到腳本所在文件夾
- 進入存放 codesnippets 的文件夾將內容複製到系統存放 codesnippets 的地方
- 進入存放 file template 的文件夾將內容複製到系統存放 file template 的地方
內容及其如何使用
- Property 屬性。敲 Property_ 自動聯想,光標移動選中後敲回車自動補全
- Mark 標識。 敲 Mark_ 自動聯想,會展示各種常用的 Mark,光標移動選中後敲回車自動補全
- Method 方法。敲 Method_ 自動聯想,會展示各種常用的 Method,光標移動選中後敲回車自動補全
- GCD。敲 GCD_ 自動聯想,會展示各種常用的 GCD,光標移動選中後敲回車自動補全
- 常用 UI 控件的懶加載。敲 _init 自動聯想,展示常用的 UI 控件的懶加載,光標移動選中後敲回車自動補全
- Delegate。敲 Delegate_ 自動聯想,會展示各種常用的 Delegate,光標移動選中後敲回車自動補全
- Notification。敲 NSNotification_ 自動聯想,會展示各種常用的 NSNotification 的代碼塊,比如發送通知、添加觀察者、移除觀察者、觀察者方法的實現等等,光標移動選中後敲回車自動補全
- Protocol。敲 Protocol_ 自動聯想,會展示各種常用的 Protocol 的代碼塊,光標移動選中後敲回車自動補全
- 內存修飾代碼塊
- 工程常用 TODO、FIXME、Mark。敲 Mark_ 自動聯想,會展示各種常用的 Mark 的代碼塊,光標移動選中後敲回車自動補全
- 內存修飾代碼塊。敲 Memory_ 自動聯想,會展示各種常用的內存修飾的代碼塊,光標移動選中後敲回車自動補全
- 一些常用的代碼塊。敲 Thread_ 等自動聯想,選中後敲回車自動補全。
使用
chmod +x ./syncSnippets.sh // 爲腳本設置可執行權限
chmod +x ./uploadMySnippets.sh // 爲腳本設置可執行權限
./syncSnippets.sh // 同步git雲端代碼塊和文件模版到本地
./uploadMySnippets.sh //將本地的代碼塊和文件模版同步到雲端
PS
不斷完善中。大家有好用或者不錯的代碼塊或者文件模版希望參與到這個項目中來,爲我們開發效率的提升添磚加瓦、貢獻力量
目前新建了大概58個代碼段和6個類文件模版(UIViewController控制器帶有方法組、模型、線程安全的單例模版、帶有佈局方法的UIView模版、UITableViewCell、UICollectionViewCell模版)
shell 腳本基本有每個函數和關鍵步驟的代碼註釋,想學習 shell 的人可以看看代碼。代碼傳送門