iOS開發(fā)規(guī)范(內(nèi)部使用)

前言

隨著公司業(yè)務(wù)的不斷增加,功能的快速迭代,app的業(yè)務(wù)線越來越多,代碼體積變得越來越龐大。同時,項目投入的開發(fā)者也越來越多,不同的開發(fā)者的code風(fēng)格千差萬別。加之公司開發(fā)者人員有可能存在變動,為了保證app穩(wěn)定性,閱讀性和擴展性,保證開發(fā)效率,統(tǒng)一開發(fā)風(fēng)格。因此,急需一篇iOS開發(fā)規(guī)范。

約定

開發(fā)規(guī)范暫時劃分成兩個等級,分別是【必須】、【建議】。

  • 【必須】:必須遵守。是不得不遵守的規(guī)范。
  • 【建議】:建議遵守。有助于維護系統(tǒng)的穩(wěn)定和提高合作效率的規(guī)范。

·規(guī)范一旦實施,CodeReview的時候如果不符合,一定要重新修改后,才可提交測試

本文參考了蘋果官方編碼指南github上一些知名的編碼規(guī)范,也算是取眾人之所長。主要由命名規(guī)范、編碼規(guī)范構(gòu)成:

一、命名規(guī)范

1.1 通用命名規(guī)則

一般情況下,通用命名規(guī)則適用于變量、常量、屬性、參數(shù)、方法、函數(shù)等。
【必須】自我描述性。屬性/函數(shù)/參數(shù)/變量/常量/宏 的命名必須具有自我描述性。杜絕中文、拼音與英文混寫、過度縮寫、或者無意義的命名方式。

【必須】駝峰命名方式。參數(shù)名、成員變量、局部變量、屬性名都要采用首字母小寫的駝峰命名方式。特殊情況除外。

【建議】一致性。屬性/函數(shù)/參數(shù)/變量/常量/宏 的命名應(yīng)該具有上下文或者全局的一致性,相同類型或者具有相同作用的變量的命名方式應(yīng)該相同或者類似。
說明:具體來講,不同文件中或者不同類中具有相同功能或相似功能的屬性的命名應(yīng)該是相同的或者相似的。好處在于:方便后來的開發(fā)者減少代碼的閱讀量和提高對代碼的理解速度。比如:

// stockCode同時定義不同類中的,該屬性都代表同一個意思,即合約代碼。
@property (readonly) NSUInteger stockCode;

【必須】清晰性。屬性/函數(shù)/參數(shù)/變量/常量/宏 的命名應(yīng)該保持清晰+簡潔,如果魚和熊掌不能兼得,那么清晰更重要。

命名 說明
removeObjectAtIndex: 規(guī)范的寫法
removeObject: 規(guī)范的寫法,因為參數(shù)指明了要移除一個對象
remove: 不清晰,移除什么?

【建議】一般情況下,不要縮寫或省略單詞,建議拼寫出來,除非有共識的縮寫(btn,bgColor,VC)。當(dāng)然,在保證可讀性的同時,for循環(huán)中遍歷出來的對象或者某些方法的參數(shù)可以縮寫。

命名 說明
customButton 規(guī)范寫法
custBut 不清晰

1.2 縮寫規(guī)范

通常,我們都不應(yīng)該縮寫命名(參考General Principles)。然而,下面所列舉的都是一些眾所周知的縮寫,我們可以繼續(xù)使用這些古老的縮寫。在其他情況下,我們需要遵循下面兩條縮寫建議:

  • 允許使用那些在C語言時代就已經(jīng)在使用的縮寫,比如allocgetc。
  • 我們可以在命名參數(shù)的時候使用縮寫。其他情況,盡量不要使用縮寫。

1.3 Method命名規(guī)范

【必須】方法名也要采用小寫字母開頭的駝峰命名方式。特殊情況除外。

【建議】類、協(xié)議、函數(shù)、常量、枚舉等全局可見內(nèi)容需要添加三個字符作為前綴。蘋果保留對任意兩個字符作為前綴的使用權(quán)。所以盡量不要使用兩個字符作為前綴。禁止使用的前綴包括但不限于:NS,UI,CG,CF,CA,WK,MK,CI,NC

【必須】禁止在方法前面加下劃線“ _ ”。Apple官網(wǎng)團隊經(jīng)常在方法前面加下劃線"_"。為了避免方法覆蓋,導(dǎo)致不可預(yù)知的意外,禁止在方法前面加下劃線。

【必須】自我描述性。方法的命名也應(yīng)該具有自我描述性。杜絕中文拼音、過度縮寫、或者無意義的命名方式。

【必須】一致性。方法的命名也應(yīng)該具有上下文或者全局的一致性,相同類型或者具有相同作用的方法的命名方式應(yīng)該相同或者類似。

【必須】所有參數(shù)前面都應(yīng)該添加關(guān)鍵字,參數(shù)之前的單詞盡量能描述參數(shù)的意義。

【必須】如果當(dāng)前子類創(chuàng)建的方法比從父類繼承來的方法更加具體明確。本身提供的方法更具有針對性。則不該重寫類本身提供的方法。而是應(yīng)該單獨的提供一個方法,并在新的方法后面添加上必要的關(guān)鍵參數(shù)。

// UIView提供的方法
- (instancetype)initWithFrame:(CGRect)frame
// 更具針對性的方法
- (instancetype)initWithFrame:(CGRect)frame mode:(int)aMode cellClass:(Class)factory Id numberOfRows:(int)rows numberOfColumns:(int)cols;

1.4 Delegate方法命名規(guī)范

如果delegate對象實現(xiàn)了另一個對象的delegate方法,那么這個對象就可以在它自己某個指定的事件發(fā)生時調(diào)用delegate對象的delegate方法。delegate方法的命名有一些與眾不同的格式:
【建議】以觸發(fā)消息的對象名開頭,省略類名前綴并且首字母小寫:

- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row;
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename;

1.5 Category命名規(guī)范

【必須】category中一般不要聲明屬性和成員變量。

【必須】避免category中的方法覆蓋系統(tǒng)方法??梢允褂们熬Y來區(qū)分系統(tǒng)方法和category方法。

1.6 Class命名規(guī)范

【建議、待定】class的名稱應(yīng)該由兩部分組成,前綴+名稱。即,class的名稱應(yīng)該包含一個前綴和一個名詞。

1.7 Notification命名規(guī)范

【必須】notification的命名使用全局的NSString字符串進行標(biāo)識。命名方式如下:
[相關(guān)類的名稱] + [Did | Will] + [可以標(biāo)識唯一的名稱] + Notification
例如:

NSApplicationDidBecomeActiveNotification

NSWindowDidMiniaturizeNotification

【必須】object通常是指發(fā)出notification的對象,如果在發(fā)送notification的同時要傳遞一些額外的信息,請使用userInfo,而不是object。

注:蘋果的初衷是通過這個object來限定哪些觀察者響應(yīng)通知的,意即object通常是指發(fā)出notification的對象,觀察者如果指定的object的內(nèi)存地址等于發(fā)送者的object,才會響應(yīng)通知(可以通過字符串測試推測出,蘋果的處理大概就是根據(jù)這個object內(nèi)存地址去判斷的)

1.8 常量命名規(guī)范

1.8.1 枚舉常量

【必須】使用枚舉類型來表示一組相關(guān)的整型常量。

typedef enum {
    /**新聞 1*/
    FSTabTypeNews = 1,
    /**精評 2*/
    FSTabTypeJingPing,
    /**解盤 3*/
    FSTabTypeJiePan,
    /**概述 4*/
    FSTabTypeGaiShu
} FSTabType;

1.8.2 使用const關(guān)鍵字創(chuàng)建常量

【必須】使用const關(guān)鍵字創(chuàng)建浮點型常量。你也可以使用const來創(chuàng)建和其他常量不相關(guān)的整型常量。否則,請使用枚舉類型來創(chuàng)建。即,如果一個整型常量和其他常量不相關(guān),可以使用const來創(chuàng)建,否則,使用枚舉類型表示一組相關(guān)的整型常量,如項目中經(jīng)常有的協(xié)議ID、pageID、控件固定尺寸等:

#define PROTOCAL_ID  1232
#define VIEW_WIDTH  200

更換為:

typedef NS_ENUM(NSInteger,Count){
    PROTOCAL_ID  = 1232,
    VIEW_WIDTH  = 200
};

1.8.3 字符串常量與宏定義

目前項目中埋點名稱等用的都是宏定義,需要優(yōu)化。
【必須】通常情況下,不要使用#define預(yù)處理命令創(chuàng)建常量。正如上面所說,對于整型常量,使用枚舉創(chuàng)建;對于浮點型常量,使用const修飾符創(chuàng)建,字符串常量使用static修飾符創(chuàng)建。

【建議】通知的名字和字典的key,應(yīng)該使用字符串常量來定義。使用字符串常量編譯器可以進行檢查,這樣可以避免拼寫錯誤

字符串常量應(yīng)該在.h頭文件中暴露給外部,而字符串常量真正的賦值是在.m文件中。如下:

.h文件
extern NSString *const KReachablityChangedNotification;
.m文件
NSString * const KReachablityChangedNotification= @"KReachablityChangedNotification";

1.9 宏定義的使用及其命名規(guī)范

1.9.1 宏定義的使用

定義系統(tǒng)控件或屏幕尺寸、判斷系統(tǒng)版本、通知名稱、通用工具類,沙盒等文件路徑等。

1.9.2 宏定義的命名規(guī)范

字母全大寫,單詞直接用“_”連接。

1.9 圖標(biāo)命名規(guī)范

基本原則是把文件名分成四部分,第一部分是圖片的邏輯歸屬分類(具體是可以體現(xiàn)圖標(biāo)位置),第二部分是圖標(biāo)的內(nèi)容的類型(具體為控件類型等),第三部分是圖片的表現(xiàn)內(nèi)容(具體可為功能、所代表意義),第四部分是表示圖片表現(xiàn)的狀態(tài)。最好不要超過四部分,根據(jù)情況可做刪減。

二、編碼規(guī)范

2.1 Init方法規(guī)范

Objective-C有designated Initializers和secondary Initializers的概念。designated Initializers叫做指定初始化方法或”全能初始化方法“。designated Initializers方法是指類中為對象提供必要信息以便其能完成工作的初始化方法。

【必須】所有secondary 初始化方法都應(yīng)該調(diào)用designated 初始化方法。

【必須】所有子類的designated初始化方法都要調(diào)用父類的designated初始化方法。使這種調(diào)用關(guān)系沿著類的繼承體系形成一條鏈。

【必須】禁止子類的designated初始化方法調(diào)用父類的secondary初始化方法。否則容易陷入方法調(diào)用死循環(huán)。如下:

【必須】另外禁止在init方法中使用self.xxx的方式訪問屬性。如果存在繼承的情況下,很有可能導(dǎo)致崩潰。

2.2 dealloc規(guī)范

【必須】不要忘記在dealloc方法中移除通知和KVO。

【必須】在dealloc方法中,禁止將self作為參數(shù)傳遞出去,如果self被retain住,到下個runloop周期再釋放,則會造成多次釋放crash。如下:

【必須】和init方法一樣,禁止在dealloc方法中使用self.xxx的方式訪問屬性。如果存在繼承的情況下,很有可能導(dǎo)致崩潰。

2.3 Block規(guī)范

【必須】block的聲明copy
【必須】調(diào)用block時需要對block判空。
【必須】注意block潛在的引用循環(huán)。

2.4 delegate規(guī)范

【必須】delegate的聲明weak

2.5 代碼縮進規(guī)范

不要在工程里使用默認(rèn) Tab 鍵,使用空格來進行縮進。在 Xcode > Preferences > Text Editing 將 Tab 和自動縮進都設(shè)置為 4 個空格

2.6 空格規(guī)范

【必須】方法類型(-/ +符號)后應(yīng)有一個空格;
【必須】參數(shù):符號前后不要有空格
【必須】運算符前后留空格;
【必須】方法大括號左邊留空格,不要另起一行;

2.7 UI規(guī)范

【必須】如果想要獲取window,不要使用view.window獲取。請使用[[UIApplication sharedApplication] keyWindow]。

【必須】在使用到 UIScrollView,UITableView,UICollectionView 的 Class 中,需要在 dealloc 方法里手動的把對應(yīng)的 delegate, dataSouce 置為 nil。

【建議】當(dāng)訪問一個 CGRectxy, width, height 時,應(yīng)該使用[CGGeometry 函數(shù)],推薦的寫法是這樣的:

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;

2.8 IO規(guī)范

【建議】盡量少用NSUserDefaults。
說明:[[NSUserDefaults standardUserDefaults] synchronize] 會block住當(dāng)前線程,知道所有的內(nèi)容都寫進磁盤,如果內(nèi)容過多,重復(fù)調(diào)用的話會嚴(yán)重影響性能。
【建議】一些經(jīng)常被使用的文件建議做好緩存。避免重復(fù)的IO操作。建議只有在合適的時候再進行持久化操作。

2.9 數(shù)組、字典等集合類規(guī)范

【必須】不要用一個可能為nil的對象初始化集合對象,否則可能會導(dǎo)致crash。

【必須】同理,對插入到集合對象里面的對象也要進行判空。

【必須】注意在多線程環(huán)境下訪問可變集合對象的問題,必要時應(yīng)該加鎖保護。不可變集合(比如NSArray)類默認(rèn)是線程安全的,而可變集合類(比如NSMutableArray)不是線程安全的。
注:自選列表就出現(xiàn)過該問題。

【必須】禁止在多線程環(huán)境下直接訪問可變集合對象中的元素。應(yīng)該先對其進行copy,然后訪問不可變集合對象內(nèi)的元素。

// 正確寫法
NSArray *array = [self.curveArray copy];
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
//do something using obj
}]; 

// 錯誤寫法
[self.curveArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    //do something using obj
    // 如果在enumerate過程中,其他線程對allItems這個可變集合進行了變更操作,這里就有可能引發(fā)crash
}]; 

【必須】注意使用enumerateObjectsUsingBlock遍歷集合對象中的對象時,關(guān)鍵字return的作用域。block中的return代表的是使當(dāng)前的block返回,而非使當(dāng)前的整個函數(shù)體返回。

說明:其實block相當(dāng)于一個匿名函數(shù),在block中使用return返回,僅是讓當(dāng)前這個匿名函數(shù)返回。

【建議】如果使用NSMutableDictionary作為緩存,建議使用NSCache代替。

【建議】具有可變副本(例如NSString、NSArray、NSDictionary)的屬性聲明應(yīng)該選擇copy而不是strong;

2.9 分支語句規(guī)范

【建議】if條件判斷語句后面必須要加大括號{}。不然隨著業(yè)務(wù)的發(fā)展和代碼迭代,極有可能引起邏輯問題。

【必須】條件表達式多于3個必須用參數(shù)抽取成多個有意義的bool變量。

【建議】遵循gold path法則,不要把真正的邏輯寫道括號內(nèi),避免多層嵌套。

// 不建議
- (void)someFuncWith:(NSString *)parameter {
    if (parameter) {
        // do something
        [self doSomething];
    }
}

// 建議
- (void)someFuncWith:(NSString *)parameter {
    if (!parameter) {
        return;
    }
    // do something
    [self doSomething];
}

【必須】使用switch...case...語句的時候,不要丟掉default:。除非switch枚舉。

【必須】switch...case...語句的每個case都要添加break關(guān)鍵字,避免出現(xiàn)fall-through。

2.10 多線程規(guī)范

【必須】禁止使用GCD的dispatch_get_current_queue()函數(shù)獲取當(dāng)前線程信息。
【必須】對剪貼板的讀取必須要放在異步線程處理,最新Mac和iOS里的剪貼板共享功能會導(dǎo)致有可能需要讀取大量的內(nèi)容,導(dǎo)致讀取線程被長時間阻塞。

【必須】禁止在非主線程中進行UI元素的操作。

【必須】在主線程中禁止進行同步網(wǎng)絡(luò)資源讀取,使用NSURLSession進行異步獲取。

【必須】如果需要進行大文件或者多文件的IO操作,禁止主線程使用,必須進行異步處理。

2.11 內(nèi)存管理規(guī)范

【建議】函數(shù)體提前return時,要注意是否有對象沒有被釋放掉(常見于CF對象),避免造成內(nèi)存泄露。

【建議】請慎重使用單例,避免產(chǎn)生不必要的常駐內(nèi)存。

內(nèi)存泄漏問題以及解決方案

(1) 控制器VC中代理的聲明出錯
代理的聲明使用weak關(guān)鍵字,如果用了retain、strong強引用聲明,有可能導(dǎo)致內(nèi)存泄漏。

(2) 控制器VC中使用NSTimer出錯

[NSTimer scheduledTimerWithTimeInterval:1.0 
                                 target:self 
                               selector:@selector(todo:) 
                               userInfo:nil 
                                repeats:YES];

NSTimer創(chuàng)建時,關(guān)鍵在于timer對target(self)進行了強引用,對象會進行retain操作。既然是被強引用了就應(yīng)該使用__weak。并在離開頁面的時候停止定時器停止并把定時器置為nil就可以解決問題。否則會導(dǎo)致對象不能釋放,內(nèi)存泄漏!
補充:
如果在非主線程的線程中只是創(chuàng)建一個NSTimer并啟動,該NSTimer是不會執(zhí)行的,除非將NSTimer加入到該線程的NSRunloop中,并啟動NSRunloop才行。

(3) 控制器VC中Block使用錯誤
Block中直接使用成員變量(self.xxx)回造成循環(huán)引用,導(dǎo)致?lián)碛性搶嵗膶ο蟛荒茚尫?。在ARC下要 __weak
注:Block一般用copy聲明,這樣會把block從棧區(qū)移到堆區(qū)。這樣,在block中進行回調(diào)或反向傳值到上個頁面時,不會出現(xiàn)對象被釋放,內(nèi)存泄露問題。

(4) 由自定義封裝的控件使用錯誤
在控制器VC中自定義的控件View的使用中傳入了當(dāng)前VC或self,造成循環(huán)引用,這種情況下pop返回時,當(dāng)前頁面也不會被釋放,dealloc也不會走。只有在離開頁面前,把該控件View先置為空nil。則可以。

(5) 避免野指針問題
僵尸對象:內(nèi)存已經(jīng)被回收的對象。
野指針:指向僵尸對象的指針,向野指針發(fā)送消息會導(dǎo)致崩潰。

(5) 檢測內(nèi)存泄漏、僵尸指針
XCode:scheme設(shè)置,Instruments
第三方庫:MLeaksFinder+FBRetainCycleDetector定位。

2.12 延遲調(diào)用規(guī)范

【必須】performSelector:withObject:afterDelay:要在有Runloop的線程里調(diào)用,否則調(diào)用無法生效。
說明:異步線程默認(rèn)是沒有runloop的,除非手動創(chuàng)建;而主線程是系統(tǒng)會自動創(chuàng)建Runloop的。所以在異步線程調(diào)用是請先確保該線程是有Runloop的。

2.13 注釋規(guī)范

  • 單行注釋
    使用 // 注釋單行代碼,最常見的使用場景是在方法內(nèi)注釋某個屬性或某塊區(qū)域的含義
  • 多行注釋
    使用 / 文本 / 的注釋格式(快捷鍵cmd+alt+/)可以對屬性和類以及方法進行注釋,與//不同的是,該注釋方式可以寫多行,一般使用在類的頭文件,多行介紹當(dāng)前類的含義

【必須】如果方法、函數(shù)、類、屬性等需要提供給外界或者他人使用,必須要加注釋說明。
【必須】如果你的代碼以SDK的形式提供給其他人使用,那么接口的注釋是必須的。必須對暴露給外界的所有方法、屬性、參數(shù)加以注釋說明。
【建議】因為方法或?qū)傩员旧砭途哂凶晕颐枋鲂?,注釋?yīng)該簡明扼要。

2.14 類的設(shè)計規(guī)范

【建議】盡量減少繼承,類的繼承關(guān)系不要超過3層??梢钥紤]使用category、protocol來代替繼承。

【建議】把一些穩(wěn)定的、公共的變量或者方法抽取到父類中。子類盡量只維持父類所不具備的特性和功能。

【建議】.h文件中盡量不要聲明成員變量。

【建議】.h文件中的屬性盡量聲明為只讀。

【建議】.h文件中只暴露出一些必要的類、公開的方法、只讀屬性;私有類、私有方法和私有屬性以及成員變量,盡量寫在.m文件中。

2.15 代碼組織規(guī)范

#pragma mark - Lifecycle
- (instancetype)init {}

#pragma mark - IBActions
- (IBAction)submitData:(id)sender {}

#pragma mark - Public
- (void)publicMethod {}

#pragma mark - Private
- (void)privateMethod {}

#pragma mark - ......Delegate

#pragma mark - setter&getter

2.16 工程結(jié)構(gòu)規(guī)范

【必須】為了避免文件雜亂,物理文件應(yīng)該保持和 Xcode 項目文件同步。

【建議】合理組織工程的內(nèi)的文件夾,工程中一般包括但不限于以下幾個文件夾category(分類)、util/helper(工具類)、resource(資源)、const(常量)、third(第三方)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容