一、命名規(guī)范
1、通用命名規(guī)范
Tips:
所有的命名都應該遵循3個基本原則,即“清晰性”、“一致性”、“不要自我指涉”。
- 清晰性:好的命名應該是能自我描述的。
正例: removeObject:、[string stringByReplacingOccurrencesOfString:@"1" withString:@"2"] 反例: remove:(不清楚,要刪除什么?)、string.replace("1", "2")(是將"1"替換成"2"還是將"2"替換成"1"?是將第1個"1"替換成"2"還是將所有的"1"都替換成"2") - 一致性:命名應該和上下文乃至全局保持一致性,相同類型或者具有相同作用的變量的命名方式應該相同或類似。
正例: NSDictionary、NSArray、NSSet這幾個集合類都是用count來表示數量而不是一個用count其它的用amount或其他單詞,這體現(xiàn)了命名的一致性。 @property (readonly) NSUInteger count; - 禁止自我指涉:命名不要自我指涉。通知、掩碼常量等除外(通常指那些可以進行按位運算的枚舉值)。
正例: NSString 反例: NSStringObject - 杜絕過度縮寫,嚴禁自創(chuàng)縮寫(例如把button縮寫為btn);國際通用縮寫名稱除外(例如ATM、GPS)。
Tips:
你明白這個縮寫的意思不代表其他人也一定會明白,你的代碼可能會被任何人閱讀,而閱讀的人來自不同的地方接受不同的教育不同的文化,所有建議一般不要亂使用縮寫,只使用那些國際通用縮寫。如果為了縮寫創(chuàng)建一個縮寫對照表只會增加代碼閱讀復雜度。
~~~
正例:
destinationSelection、setBackgroundColor
反例:
destSel、setBgColor
~~~ - 杜絕無意義的拼音,國際通用名稱或者地名人名除外(例如alibaba、taobao、hangzhou)。
反例: DaZhePromotion(打折) - 命名要盡可能的清晰并簡潔,如果兩者不能兼得,則以清晰為主。
正例: insertObject:atIndex: 反例: insert:at:(不清晰,插入什么?at代表什么?) - 代碼和注釋中都要避免使用任何語言的種族歧視性詞語。
正例: secondary 反例: slave - 類名、協(xié)議名、函數名、常量名、枚舉名等一些全局命名需要添加前綴,前綴需要大于2個字符且全部大寫。
Tips: 系統(tǒng)保留任意兩個字符作為前綴的使用權,
包括但不限于NS、UI、CG、CF、CA、WK、MK、CI、NC;前綴若等于2個字符可以考慮添加_。
~~~
正例:
ZT_LoginViewController
反例:
ZTLoginViewController
~~~ - 類名、協(xié)議名、函數名、常量名、枚舉名等一些全局命名遵循首字母大寫的駝峰命名方式,首個單詞是HTTP這種特殊詞除外。
- 方法名、屬性名等一些非全局命名遵循首字母小寫的駝峰命名方式命名,首個單詞是HTTP這種特殊詞除外。
- 成員變量需要以_開頭。
正例: NSString *_nameString; - 在給常量或變量命名時,盡量將表示類型的名詞放在詞尾,以提升辨識度。
正例: nameLabel、nameString 反例: name(name是字符串還是什么?) - 如果模塊、接口、類、方法使用了模式,在命名時盡量體現(xiàn)出具體模式。
正例: OrderFactory、LoginProxy - 局部臨時變量命名可以加上標識符作為前綴。
正例: t_label、t_string(t在這里表示temp)
2、類命名規(guī)范
- 類名命名風格由"前綴+類的名稱+類的類型"3個部分組成,前綴必須大于2個字符且全部大寫(如果等于2個字符可以添加_);類的名稱遵循首字母大寫駝峰式命名,類的名稱要能表達出該類的功能;類的類型必須使用全稱,嚴禁使用縮寫(例如vc代替viewController,cell代替TableViewCell),命名方式和名稱命名一樣首字母大寫。
正例: WXYZ_LoginViewControler WXYZ_表示前綴,Login表示該類跟登錄相關,ViewController表示該類是一個視圖控制器而不是View。
3、方法命名規(guī)范
- 所有方法名稱禁止以new開始。
- 所有方法名稱禁止使用開始。
Tips: 系統(tǒng)會使用開頭命名一些系統(tǒng)私有方法 - 內部私有方法需要增加前綴,前綴需要保持唯一性(例如p_)。
Tips: 給私有方法加前綴有2個好處:
- 增加辨識度,提高代碼可讀性。
- 避免自己的方法無意間覆蓋了系統(tǒng)/框架同名的私有方法。
- 如果方法返回接收者的某個屬性值,那么請直接使用屬性名作為方法名。
正例: - (CGSize)cellSize; 反例: - (CGSize)getCellSize; - 如果方法間接返回一個或多個值,那么請用"getXXX"命名,這種命名只適用于返回多個數據項的情況。
正例: - (void)getCachedResponseForDataTask:(NSURLSessionDataTask *)dataTask completionHandler:(void (^) (NSCachedURLResponse * __nullable cachedResponse))completionHandler; - 方法的每個參數前都必須添加關鍵字。
正例: - (void)sendAction:(SEL)aSelector toObject:(id)anObject forAllCells:(BOOL)flag; 反例: - (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag; - 參數之前的單詞盡量能描述參數的意義。
正例: - (id)viewWithTag:(NSInteger)aTag; 反例: - (id)taggedView:(int)aTag; - 請不要使用“and”連接接收者屬性,盡管and讀起來還算順口,但隨著你創(chuàng)建的方法參數的增加,這將會帶來一系列的問題。
正例: - (int)runModalForDirectory:(NSString *)path file:(NSString *) name types:(NSArray *)fileTypes; 反例: - (int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes; - 如果方法描述了兩個獨立的動作,則可以使用"and"連接起來。
正例: - (BOOL)openFile:(NSString *)fullPath withApplication:(NSString *)appName andDeactivate:(BOOL)flag;
4、Protocol命名規(guī)范
- Protocol中的方法命名以觸發(fā)消息的對象名開頭,省略類名前綴并首字母小寫,如果它沒有關聯(lián)任何類則可以忽略這個規(guī)則。
正例: - (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row; - (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename; - 除非Protocol方法只有一個參數,否則冒號需緊跟在類名后面。
正例: - (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row; - (BOOL)applicationOpenUntitledFile:(NSApplication *)sender;
5、Category命名規(guī)范
- 分類命名也要和類命名一樣添加前綴。
正例: UIView (YYAdd) 反例: UIView (Add) - 分類中聲明的方法名都要加上前綴。
- Category中盡量不要聲明屬性,能挪盡量挪到主類中聲明。
Tips: 盡管從技術上來講可以在分類中聲明屬性,但是這么做需要格外小心,
因為它很容易出現(xiàn)內存上或其他一些問題,而且一旦出現(xiàn)問題很難排查。 - 如果一個類比較復雜,那么建議使用分類組織代碼(可以參考系統(tǒng)的UIView)。
6、Notification命名規(guī)范
- Notification的命名風格由"類名前綴" + "通知事件名稱" + "Notification"3個部分組成。
正例: UIApplicationDidBecomeActiveNotification UIApplication表示該通知屬于誰,DidBecomeActive表示該通知的作用,Notification表示它是一個通知。 - 如果一個類聲明了delegate屬性,通常情況下,這個類的delegate對象應該可以通過實現(xiàn)的delegate方法收到大部分通知消息。
Tips:
例如applicationDidBecomeActive:代理方法和NSApplicationDidBecomeActiveNotification通知(這其實也符合命名規(guī)范的基本原則"一致性")。
7、常量命名規(guī)范
- 如果常量局限于某"編譯單元"之內,通常在前面加小寫字母k作為前綴,若常量在全局可見,通常以類名作為前綴,然后采用首字母大寫的駝峰式命令風格。
正例: // 局部可見 const CGFloat kAnimationDuration = 2.0; // 全局可見 const CGFloat UIActivityIndicatorViewAnimationDuration = 2.0;
8、Exception命名規(guī)范
- 命令規(guī)范和Notification一樣,把后綴改為"Exception"即可。
9、文件命名規(guī)范
- 文件名全部小寫。
- 采用_連接單詞。
- 命名的風格:"模塊_屬性描述",可根據項目適當增加描述。
正例: public_back@2x.png
二、編碼規(guī)范
1、通用編碼規(guī)范
- 如果有使用到CF(Core Foundation)等框架時,或者是在iOS10以下系統(tǒng)使用通知和KVO時,切記在dealloc方法中釋放對象以及移除通知和監(jiān)聽。
- 在dealloc方法內禁止將self傳遞出去,如果self被retain,到下個runloop周期再釋放則會多次釋放導致crash。
反例: - (void)dealloc { [self unsafeMethod:self]; } - 禁止使用過時的方法或類,應該及時去了解和使用新方法或類。
Tips:
對于過時的方法或類,大都是因為其自身有一些缺陷或BUG才會不建議使用,使用新方法時建議了解一下為什么廢棄掉舊方法/類。
對剪切板的讀取操作必須放在子線程中進行,因為用戶可能在Mac上復制大量數據然后通過iCloud同步到iPhone上。
if、else、for、while、case等后面必須要有{},除非后面是簡單的return類型語句,例如if (xxx) return;。
-
當方法可能會提前return時,需要要注意對象的釋放問題,避免內存泄漏。
反例: CFArrayRef arrayRef = (__bridge CFArrayRef)array; if (x == YES) return; CFRelease(arrayRef); 以上代碼如果x等于YES的話那么arrayRef對象就會內存泄漏。 -
當使用@try處理異常時,需要要注意對象的釋放問題,避免內存泄漏。
反例: @try { CFArrayRef arrayRef = (__bridge CFArrayRef)array; do some thing…… CFRelease(arrayRef); } @catch (NSException *exception) { } 以上代碼如果do some thing……出現(xiàn)異常的話那么arrayRef就會出現(xiàn)內存泄漏。 -
聲明常量請使用const類型聲明,禁止使用#define宏定義。
Tips:
宏定義聲明常量的缺點:- 宏定義只是簡單的替換,缺少編譯檢查,運行期可能會出現(xiàn)溢出或數據錯誤等問題。
- 宏定義缺少類型,不方便編寫文檔用例。
- 宏定義可能會被不小心替換。
- 宏定義無法編寫注釋。
反例: #define kTime @"10" if (1 == 2) { #define kTime @"20" } NSLog(@"time = %@", kTime); 以上代碼中的if永遠不會執(zhí)行,但是編譯器也會將kTime替換為@"20" -
寫一些公共方法時,請盡量使用內聯(lián)函數或者全局函數而不是宏定義。
Tips:
函數不通過對象調用,所以不會走OC的消息轉發(fā)流程,效率遠高于方法調用;而且函數會有返回值和參數類型以及參數檢查,而這些都是宏定義沒有的。
正例: UIKIT_STATIC_INLINE NSString * kNSStringFromInteger(NSInteger a) { return [NSString stringWithFormat:@"%zd", a]; } 反例: #define kNSStringFromInteger(a) [NSString stringWithFormat:@"%zd", a] UITableView使用self-sizing實現(xiàn)不等高cell時,請在tableView:cellForRowAtIndexPath:代理方法中給cell設置數據而不是tableView:willDisplayCell:forRowAtIndexPath:代理方法中設置數據。
-
只在必要的時刻使用懶加載。
Tips:
只在以下三種情況下才能使用懶加載:- 對象的創(chuàng)建需要依賴其他對象
- 對象可能被使用,也可能不被使用
- 對象創(chuàng)建比較消耗性能
懶加載方法內應該只執(zhí)行需要初始化的操作,不應該有其他不必要的邏輯代碼。
-
使用一目運算符時左右兩邊不能有空格。
正例: i++、++i、 反例: i ++、++ i -
使用二目、三目運算符時左右兩邊必須有且僅有一個空格。
正例: 1 + 2 反例: 1+2 采用4個空格縮進,如果要使用Tab字符,請將1個Tab設置成4個空格。
使用NSUserDefaults存儲數據時禁止調用synchronize方法,因為系統(tǒng)會在合適的時機將數據保存到本地(即使程序閃退等極端情況)。
-
添加到集合中的對象應該是不可變的,或者在加入之后其哈希碼是不可變的。
反例: NSMutableSet *sets = [NSMutableSet set]; NSMutableString *string1 = [NSMutableString stringWithString:@"1"]; [sets addObject:string1]; [sets addObject:@"12"]; [string1 appendString:@"2"]; 當 [string1 appendString:@"2"] 執(zhí)行完以后sets對象內會包含2個@"12"。 -
必須使用CGRectGet獲取Frame的各種值,而不是通過frame.的方式獲取。
Tips:
CGRect t_frame = CGRectMake(-10, -10, -10, -10);
當一個view的frame設置成t_frame后,其坐標會隱式的轉換為CGRectMake(-20, -20, 10, 10),因為寬高不可能出現(xiàn)負值;這時通過t_frame.的方式獲取的值都是錯誤的,而CGRectGet會自動幫您處理這些隱式轉換。
正例: CGRectGetWidth(frame)、CGRectGetMinX(frame)、CGRectGetMaxX(frame) 反例: frame.size.width、frame.origin.x、frame.size.width + frame.origin.x -
單行字符數限制不超過150個,超出需要換行(空格可以除外),換行時遵循如下原則:
Tips:
- 第二行相對第一行縮進4個空格,從第三行起不再繼續(xù)縮進。
- 運算符與下文一起換行。
- 方法調用的點符號與下文一起換行。
正例: - (void)setImageWithURL:(nullable NSURL *)imageURL placeholder:(nullable UIImage *)placeholder options:(YYWebImageOptions)options progress:(nullable YYWebImageProgressBlock)progress ransform:(nullable YYWebImageTransformBlock)transform completion:(nullable YYWebImageCompletionBlock)completion; 不可變對象盡量使用copy修飾,如果重寫使用copy修飾的set方法,請注意調用copy方法。
對于一些體積小并且重要的信息,不要頻繁的存儲到本地,可以使用NSUserDefaults進行存儲。它會在合適的時機存儲到本地,這避免了頻繁的寫入操作,而且在某些極端情況下它也能保證數據存儲到本地(例如程序閃退等情況)。
在多線程環(huán)境下謹慎使用可變集合,必要時候可以采用加鎖或GCD的同步線程進行保護,或者在訪問可變集合時先將其copy為不可變對象然后再對其訪問。
頭文件中盡量不要聲明成員變量而是使用屬性代替。
-
頭文件中的屬性盡量聲明為只讀,可以在實現(xiàn)文件中再將屬性聲明為可讀可寫。
正例: @interface WXYZModel : NSObject @property (nonatomic, readonly) NSString *name; @end @interface WXYZModel () @property (nonatomic, strong) NSString *name; @end -
不要使用一個類去維護多個類的內容,例如一個常量類維護所有的常量類,要按常量功能進行歸類,分開維護。
Tips: 大而全的類,雜亂無章,使用查找功能才能定位到具體位置,不利于理解也不利于維護。正例: 緩存相關常量類放在CacheCosts下,系統(tǒng)配置相關常量類放在SystemConfigConsts下。 如果大括號內為空,則簡潔的寫成{}就行。
-
沒有必要增加多余空格來使上下代碼的等號對齊。
正例: int a1 = 1; long a2 = 3; NSString *a3 = @""; 反例: int a1 = 1; long a2 = 3; NSString *a3 = @""; -
少用if else,可以使用 if return 替換,if 嵌套最好不超過5層。
正例: if (x == 1) { …… return; } if (x == 2) { …… return; } 反例: if (x == 1) { …… } else if (x == 2) { …… } -
盡量避免采用取反邏輯運算符,因為取反邏輯不利于快速理解。
正例: if (array == nil) { …… } 反例: if (!array) { …… } 如果用到了很多協(xié)議,必要時可以把協(xié)議封裝到一個單獨的頭文件中,這樣做不僅可以減小編譯時間,還能避免循環(huán)引用。
使用Switch枚舉時盡量將所有枚舉類型都列舉出來而不使用default,這樣下次增加枚舉類型時如果Switch沒有處理會有警告信息。
-
盡量使用字面量語法創(chuàng)建對象,少用與之等價的方法。
Tips:
OC中的NSArray、NSString、NSDictionay、NSNumber都有與之對應的字面量語法: @[]、@""、@{}、@();
使用它們有以下優(yōu)點: > 1. 簡單易讀,提高代碼的可讀性和可維護性。 > 1. 使用字面量創(chuàng)建數組、字典時如果元素里在nil則會拋出異常,而使用arrayWithObjects:這些等價方法創(chuàng)建則會丟失nil后的數據,拋出異常能讓你知道這里有問題及時修改防止問題在線上發(fā)生。 缺點: 1. 使用字面量創(chuàng)建的對象默認是不可變的,如果要創(chuàng)建可變對象需要進行mutableCopy操作。 2. 不支持子類,如果你創(chuàng)建了一個NSString的子類,@""并不會返回你想要的子類對象。 頭文件中盡量少引用其他頭文件,盡量使用@class向前聲明,每次引入其他頭文件時問問自己是否必須要這樣做。
UI控件建議使用weak修飾而不是strong修飾。
類編碼規(guī)范如果超類的某個初始化方法不適用于子類,那么子類一定要覆寫超類的這個方法并解決該問題或拋出異常。
盡量不要使用load類方法,如果必須要使用不能在方法內實現(xiàn)復雜邏輯或堵塞線程。
盡量減少繼承,類的繼承盡量不要超過3層,必要時刻可以考慮用分類、協(xié)議來代替繼承。
把一些穩(wěn)定的、公共的變量或者方法抽取到父類中。子類盡量只維持父類所不具備的特性和功能。
2、方法編碼規(guī)范
- 禁止在init等初始化方法內部、getter、setter、dealloc或其他特殊地方使用.語法訪問屬性。
Tips: 當存在繼承關系時使用.語法訪問會因為多態(tài)關系調用子類的實現(xiàn)方法,而如果這個時候子類還沒有初始化好或者已經釋放了那么可能會出現(xiàn)一些奇怪的問題。
- 方法參數在定義和傳入時,逗號后面必須添加一個空格。
正例: method(a1, a2, a3); - 單個方法的行數建議不超過80行,注釋、左右大括號、空行、回車等除外。
- 在實現(xiàn)文件內部也盡量使用.語法訪問屬性而不是使用_直接訪問成員變量來保證風格統(tǒng)一。
3、Block編碼規(guī)范
- 調用Block必須判空處理。
Tips:
對于簡單的Block可以使用三目運算進行判空處理,
例如 !self.block ?: self.block(); - 在Block內部使用外部變量時要注意循環(huán)引用的問題。
Tips:
- 不一定在Block內使用self才會循環(huán)引用,如下情況也會造成循環(huán)引用:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { WXYZ_TitleTableViewCell *cell = ……… cell.refreshTableViewBlock = ^{ [tableView reloadData]; }; return cell; }- Block內部是否要使用weak需要看Block本身和weak的這個對象是否存在直接或間接的相互引用,若無相互引用則不需要使用weak。
- 如果Block內部使用了strong修飾了外部的weak變量,那么當使用strong指向成員變量時需要進行判空,否則會崩潰,參考以下代碼:
__weak typeof(self) weakSelf = self; cell.refreshTableViewBlock = ^{ __strong typeof(weakSelf) strongSelf = weakSelf; if (strongSelf != nil) { strongSelf->_name = @"name"; } };如果把(strongSelf != nil)的判斷去掉那么可能會崩潰。
5、通知編碼規(guī)范
- 在發(fā)送通知時,請使用userInfo進行傳值,而不是object。
- 通知中心是以同步的方式發(fā)送請求的,所以不要在通知方法做一些復雜的計算,特別是當它處于主線程的時候,如果想發(fā)送異步通知可以使用NSNotificationQueue。
- 在工程里能不用通知盡量不用通知,通知雖然靈活強大,但是如果濫用會導致工程質量下降,出現(xiàn)問題時也比較難排查。
6、注釋編碼規(guī)范
- 與其絞盡腦汁寫注釋,不如想想怎么命名;注釋是起輔助作用的,好的命名應該是能自我解釋的,如果命名可以解釋其作用,并且方法沒有任何副作用或者注意事項,那么就不用寫注釋;注釋應該幫助別人更快的理解該方法的使用和注意事項,如果該方法有需要注意的地方一定要在注釋中體現(xiàn)出來。
- 當修改了方法實現(xiàn)時需要同步修改注釋內容。
- 注釋不要寫的太冗長,要簡單易讀容易理解。
- 注釋的雙斜線和內容之間有且僅有一個空格。
正例: // 這是示例注釋,請注意在雙斜線后有一個空格 - (void)testFunction; - 對于代碼注釋需謹慎,代碼被注釋一般有2種可能,1) 后續(xù)會恢復此段代碼邏輯; 2) 永久不用;對于第1種情況需添加相應注釋,如果沒有注釋信息難以知曉注釋動機,后者建議直接刪除。如果有需要可以通過代碼倉庫查閱歷史代碼。
- 使用特殊注釋標記時,請注明標記人和標記時間,注意及時處理這些標記。
正例: /** * @brief 簡要描述 * @author 標明開發(fā)該類模塊的作者 */ // FIXME: 有bug,需要修改 - (void)testFunction; - 別給糟糕的代碼加注釋,重構它。
Tips: 注釋不能美化糟糕的代碼。當企圖使用注釋前,先考慮是否可以通過調整結構,命名等操作,消除寫注釋的必要。
三、工程結構規(guī)范
- 局部使用的常量、靜態(tài)變量聲明在@interface之前。
- @property同一類型的聲明放在一塊,不同類型的聲明用2行空格隔開。
正例: @interface MineViewController () @property (nonatomic, weak) UIView *headView; @property (nonatomic, weak) UITableView *tableView; 我是換行符,請忽略 @property (nonatomic, copy) NSArray *dataSourceArray; - 不同邏輯、不同語義、不同業(yè)務的代碼之間插入一個空行分隔開以提升可讀性。
正例: [self createSubviews]; [self createTableview]; [self netRequest]; - 方法歸類
#pragma mark - LifeCycle(生命周期相關的代碼放在最上面) - (void)dealloc {} - (void)viewDidLoad {} - (void)viewWillAppear:(BOOL)animated {} #pragma mark - Public(公開方法) // code... // 上空一行 // 下空兩行 #pragma mark - Private(私有方法) #pragma mark - Override(需要覆蓋父類的方法) #pragma mark - Notification(通知方法) #pragma mark - Delegate(Delegate需要實現(xiàn)的方法) #pragma mark - getter/setter