iOS 開發(fā)規(guī)范

前言
說是前言,其實(shí)也是本文誕生的目的。隨著公司業(yè)務(wù)的不斷增加,功能的快速迭代,app的業(yè)務(wù)線越來越多,代碼體積變得越來越龐大。同時(shí),app投入的開發(fā)者也也越來越多,不同的開發(fā)者的code風(fēng)格千差萬別。加之公司開發(fā)者人員變動(dòng),為了保證app穩(wěn)定性,保證開發(fā)效率,統(tǒng)一開發(fā)風(fēng)格。于是,這篇iOS開發(fā)規(guī)范應(yīng)運(yùn)而生。
因筆者現(xiàn)在所就職公司的開發(fā)規(guī)范主導(dǎo)編寫,目前公司業(yè)務(wù)的迭代都在按照這個(gè)規(guī)范在有條不紊的進(jìn)行。綜合之前編寫規(guī)范的經(jīng)驗(yàn),歷時(shí)一個(gè)月的時(shí)間,斷斷續(xù)續(xù)重新梳理了一份比較全面、比較完整的iOS開發(fā)者規(guī)范,希望這些條條框框能夠給正在閱讀的你提供一些參考的價(jià)值。也希望越來越多的iOS開發(fā)者能夠養(yǎng)成優(yōu)秀的編碼習(xí)慣。如果你覺得個(gè)別地方不妥或者有需要補(bǔ)充的規(guī)范,請留言或者私信,我會(huì)第一時(shí)間響應(yīng)。

約定

在我看來,開發(fā)規(guī)范像是一條可供參考的標(biāo)準(zhǔn)線。不同開發(fā)者可以根據(jù)這條標(biāo)準(zhǔn)線來規(guī)范自己的開發(fā)行為,尤其是在大的項(xiàng)目中,開發(fā)規(guī)范可以約束不同開發(fā)者的開發(fā)風(fēng)格,使項(xiàng)目從細(xì)節(jié)到整體上都能達(dá)到風(fēng)格統(tǒng)一,利于維護(hù)。
本文的開發(fā)規(guī)范由很多item組成,不同的item描述了不同的問題。每一個(gè)item就是一條具體的開發(fā)規(guī)范,違反不同的開發(fā)規(guī)范,也會(huì)引起不同嚴(yán)重程度的后果。就像法律和道德的差異一樣,我們必須遵守法律,不然可能帶來損人不利己的嚴(yán)重后果,但有些人雖然沒有觸犯法律,卻違背了道德,雖然暫時(shí)沒有產(chǎn)生嚴(yán)重的后果,長此以往,也會(huì)形成一種壞的風(fēng)氣。所以,無論法律和道德,我們都該鞭策自己成為優(yōu)秀的人,而不該止步于一個(gè)合格的人。同理,開發(fā)規(guī)范也是如此,我們必須遵守那些必須要遵守的開發(fā)規(guī)范,提倡遵守那些建議你遵守的開發(fā)規(guī)范。所以,根據(jù)約束力度,我們把開發(fā)規(guī)范暫時(shí)劃分成兩個(gè)等級,分別是【必須】、【建議】。

  • 【必須】:必須遵守。是不得不遵守的約定,一旦違反極有可能引起嚴(yán)重后果。
  • 【建議】:建議遵守。長期遵守這樣的約定,有助于維護(hù)系統(tǒng)的穩(wěn)定和提高合作效率。

本文參考了蘋果官方編碼指南和github上一些知名的編碼規(guī)范,也算是取眾人之所長,集百家之精華的一篇文章。讀者可以根據(jù)自己的實(shí)際需要和興趣點(diǎn)來選擇性的閱讀。本文主題部分主要由以下兩章(共32節(jié))構(gòu)成:

(一) 命名規(guī)范
  1. 通用命名規(guī)范(講述命名的一些通用規(guī)范)
  2. 縮寫規(guī)范(講述常見的縮寫以及縮寫規(guī)范)
  3. Method命名規(guī)范(講述方法命名的具體規(guī)范)
  4. Accessor命名規(guī)范(講述set和get方法的命名規(guī)范)
  5. Parameter命名規(guī)范(講述參數(shù)命名規(guī)范)
  6. Delegate方法命名規(guī)范(講述delegate方法的命名規(guī)范)
  7. Private方法命名規(guī)范(講述私有方法的命名規(guī)范)
  8. Category命名規(guī)范(講述分類的命名規(guī)范)
  9. Class命名規(guī)范(講述類命名規(guī)范)
  10. Protocol命名規(guī)范(講述協(xié)議的命名規(guī)范)
  11. Notification命名規(guī)范(講述通知的命名規(guī)范)
  12. Constant命名規(guī)范(講述枚舉常量以及const常量的命名規(guī)范)
  13. Exception命名規(guī)范(講述異常的命名規(guī)范)
(二)編碼規(guī)范
  1. Initialize方法(講述類的initialize方法的使用規(guī)范)
  2. Init方法(講述初始化方法的設(shè)計(jì)規(guī)范包括designated init方法和secondary init方法)
  3. Init error(講述init方法初始化對象失敗時(shí)的錯(cuò)誤處理)
  4. Dealloc規(guī)范(講述dealloc方法的使用規(guī)范)
  5. Block規(guī)范(講述block的使用規(guī)范)
  6. Notification規(guī)范(講述通知的使用規(guī)范)
  7. UI規(guī)范(講述開發(fā)UI時(shí)的一些規(guī)范)
  8. IO規(guī)范(講述讀寫文件時(shí)的一些注意事項(xiàng))
  9. Collection規(guī)范(講述集合類型的使用規(guī)范)
  10. 分支語句規(guī)范(講述常用的分支語句if、switch語句的編碼規(guī)范)
  11. 對象判等規(guī)范(講述常用的判定對象等同性的方法使用規(guī)范)
  12. 懶加載規(guī)范(講述懶加載的使用規(guī)范)
  13. 多線程規(guī)范(講述多線程環(huán)境下的一些編碼規(guī)范)
  14. 內(nèi)存管理規(guī)范(講述編碼過程中常見的內(nèi)存管理注意點(diǎn))
  15. 延遲調(diào)用規(guī)范(講述使用延遲方法時(shí)注意事項(xiàng))
  16. 注釋規(guī)范(講述編碼中注釋的使用規(guī)范)
  17. 類的設(shè)計(jì)規(guī)范(講述類的設(shè)計(jì)規(guī)范)
  18. 代碼組織規(guī)范(講述類中的代碼組織規(guī)范)
  19. 工程結(jié)構(gòu)規(guī)范(講述工程的文件組織規(guī)范)

(一)命名規(guī)范

根據(jù)Cocoa編碼規(guī)范里的描述,以前情況下,命名應(yīng)該遵循以下基本原則:Clarity、Consistency、No Self Reference。即清晰性、一致性、不要自我指涉Code Naming Basics。

(1.1) 通用命名規(guī)則

一般情況下,通用命名規(guī)則適用于變量、常量、屬性、參數(shù)、方法、函數(shù)等。當(dāng)然也有例外,下面我們會(huì)針對于每一種情況一一列舉。
【必須】自我描述性。屬性/函數(shù)/參數(shù)/變量/常量/宏 的命名必須具有自我描述性。杜絕中文拼音、過度縮寫、或者無意義的命名方式。

【必須】禁止自我指涉。屬性/局部變量/成員變量不要自我指涉。通知和掩碼常量(通常指那些可以進(jìn)行按位運(yùn)算的枚舉值) 除外。
通俗的講,自我指涉是指在變量末尾增加了自己類型的一個(gè)后綴。

命名 說明
NSString 規(guī)范的寫法
NSStringObject 自我指涉(不規(guī)范)

掩碼常量、通知除外:

命名 說明
NSUnderlineByWordMask 規(guī)范的寫法
NSTableViewColumnDidMoveNotification 自我指涉(不規(guī)范)

【必須】駝峰命名方式。參數(shù)名、成員變量、局部變量、屬性名都要采用小寫字母開頭的駝峰命名方式。如果方法名以一個(gè)眾所周知的大寫縮略詞開始,可以不適用駝峰命名方式。比如FTP、WWW等。

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

// count同時(shí)定義在NSDictionary、NSArray、NSSet這三個(gè)集合類中。且這三個(gè)集合類中的count屬性都代表同一個(gè)意思,即集合中對象的個(gè)數(shù)。
@property (readonly) NSUInteger count;

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

命名 說明
insertObject:atIndex: 規(guī)范的寫法
insert:at: 不清晰,插入什么?at代表什么?
removeObjectAtIndex: 規(guī)范的寫法
removeObject: 規(guī)范的寫法,因?yàn)閰?shù)指明了要移除一個(gè)對象
remove: 不清晰,移除什么?

建議】一般情況下,不要縮寫或省略單詞,建議拼寫出來,即使它有點(diǎn)長。當(dāng)然,在保證可讀性的同時(shí),for循環(huán)中遍歷出來的對象或者某些方法的參數(shù)可以縮寫。

命名 說明
destinationSelection 規(guī)范的寫法
destSel 不清晰
setBackgroundColor: 規(guī)范的寫法
setBkgdColor: 不清晰

(1.2) 縮寫規(guī)范

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

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

我們也可以使用計(jì)算機(jī)行業(yè)通用的縮寫。包括但不限于HTML、URL、RTF、HTTP、TIFF、JPG、PNG、GIF、LZW、ROM、RGB、CMYK、MIDI、FTP。

(1.3) Method命名規(guī)范

必須】方法名也要采用小寫字母開頭的駝峰命名方式。如果方法名以一個(gè)中所周知的大寫縮略詞開頭(比如HTTP),該規(guī)則可以忽略。

【建議】一般情況下,不要在方法名稱中使用前綴,因?yàn)樗嬖谟谔囟惖拿臻g中。

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

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

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

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

// 該方法同時(shí)定義在NSView、NSControl、NSCell這三個(gè)類里面。
- (NSInteger)tag;
// 該屬性同時(shí)定義在NSDcitionary和NSArray中。
@property (readonly) NSUInteger count;

【必須】蘋果爸爸說:如果一個(gè)方法代表某個(gè)名詞執(zhí)行的動(dòng)作,則該方法應(yīng)該以一個(gè)動(dòng)詞開頭。如下:

- (void)invokeWithTarget:(id)target;
- (void)selectTabViewItem:(NSTabViewItem *)tabViewItem

【必須】蘋果爸爸還說:如果方法代表對象接收的動(dòng)作,那么方法一動(dòng)詞開頭。但不要使用“do”或者"does"作為方法名稱的一部分,因?yàn)檫@些助動(dòng)詞不能為方法名稱增加太多的意義,反而讓方法看起來更加臃腫。同時(shí),也請不要在動(dòng)詞前面使用副詞或者形容詞。

【必須】如果方法返回接收者的某個(gè)屬性,那么請直接以屬性名作為方法名。如果方法間接的返回一個(gè)或多個(gè)值,我們可以使用“getxxx”的方式來命名方法。相反,無需額外的在方法名前面添加"get"。

命名 說明
- (NSSize)cellSize; OK
- (NSSize)calcCellSize; 不OK
- (NSSize)getCellSize; 不OK

【必須】只有當(dāng)方法間接的返回對象或數(shù)值,才有必要在方法名稱中使用“get”,這種格式只適用于返回多個(gè)數(shù)據(jù)項(xiàng)的情況。如下:

// 通過傳入指針,來獲得多個(gè)值
 - (void)getLineDash:(float *)pattern count:(int*)count phase:(float *)phase; 
 // NSURLCache (NSURLSessionTaskAdditions)中聲明的方法
 - (void)getCachedResponseForDataTask:(NSURLSessionDataTask *)dataTask completionHandler:(void (^) (NSCachedURLResponse * __nullable cachedResponse))completionHandler;

【必須】所有參數(shù)前面都應(yīng)該添加關(guān)鍵字,除非你能保證每個(gè)人都能意會(huì)到你的精神。

命名 說明
- (void)sendAction:(SEL)aSelector toObject:(id)anObject forAllCells:(BOOL)flag; OK
- (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag; 不OK

【建議】蘋果爸爸說:參數(shù)之前的單詞盡量能描述參數(shù)的意義。

命名 說明
- (id)viewWithTag:(NSInteger)aTag; OK
- (id)taggedView:(int)aTag; 不OK

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

命名 說明
- (id)initWithFrame:(CGRect)frameRect; NSView, UIView.
- (id)initWithFrame:(NSRect)frameRect mode:(int)aMode cellClass:(Class)factoryId numberOfRows:(int)rowsHigh numberOfColumns:(int)colsWide; NSMatrix, a subclass of NSView
// UIView提供的方法
- (instancetype)initWithFrame:(CGRect)frame 
// 更具針對性的方法
- (instancetype)initWithFrame:(CGRect)frame mode:(int)aMode cellClass:(Class)factory Id numberOfRows:(int)rows numberOfColumns:(int)cols;

【建議】請不要使用“and”連接接收者屬性。盡管and在下面的例子中讀起來還算順口,但隨著你創(chuàng)建的方法參數(shù)的增加,這將會(huì)帶來一系列的問題。

命名 說明
- (int)runModalForDirectory:(NSString *)path file:(NSString *) name types:(NSArray *)fileTypes; OK
- (int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes; 不OK

【建議】如果方法描述了兩個(gè)獨(dú)立的動(dòng)作,可以使用“and”連接起來。

命名 說明
- (int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes; OK (NSWorkspace. )

(1.4) Accessor命名規(guī)范

Accessor Methods是指set、get方法。這些方法有一些推薦寫法格式:

【建議】如果屬性是名詞,推薦格式如下:

- (type)noun;
- (void)setNoun:(type)aNoun;
- (NSString *)title;
- (void)setTitle:(NSString *)aTitle;

【必須】不要把動(dòng)詞的過去分詞形式當(dāng)做形容詞來使用

命名 說明
- (void)setAcceptsGlyphInfo:(BOOL)flag; OK
- (BOOL)acceptsGlyphInfo; OK
- (void)setGlyphInfoAccepted:(BOOL)flag; 不OK
- (BOOL)glyphInfoAccepted; 不OK
命名 說明
- (void)setCanHide:(BOOL)flag; OK
-(BOOL)canHide; OK
- (void)setShouldCloseDocument:(BOOL)flag; OK
- (BOOL)shouldCloseDocument; OK
- (void)setDoesAcceptGlyphInfo:(BOOL)flag; 不OK
- (BOOL)doesAcceptGlyphInfo; 不OK

【建議】可以使用情態(tài)動(dòng)詞(can、should、will等)明確方法意義,但不要使用do、does這類無意義的情態(tài)動(dòng)詞。

命名 說明
- (void)setCanHide:(BOOL)flag; OK
- (BOOL)canHide; OK
- (void)setShouldCloseDocument:(BOOL)flag; OK
- (BOOL)shouldCloseDocument; OK
- (void)setDoesAcceptGlyphInfo:(BOOL)flag; 不OK
- (BOOL)doesAcceptGlyphInfo; 不OK

【建議】只有方法間接的返回一個(gè)數(shù)值,或者需要多個(gè)數(shù)值需要被返回的時(shí)候,才有必要在方法名稱中使用“get”。
像這種接收多個(gè)參數(shù)的方法應(yīng)該能夠傳入nil,因?yàn)檎{(diào)用者未必對每個(gè)參數(shù)都感興趣

- (void)getLineDash:(float *)pattern count:(int *)count phase:(float *)phase;

(1.5) Parameter命名規(guī)范

【必須】不要使用 ”pointer” 或 ”ptr” 命名參數(shù),應(yīng)該使用參數(shù)類型而非它的名字來代表他是否是一個(gè)指針。Method Arguments

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

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

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

【建議】除非delegate方法只有一個(gè)參數(shù),即觸發(fā)delegate方法調(diào)用的delegating對象,否則冒號是緊跟在類名后面的。

- (BOOL)applicationOpenUntitledFile:(NSApplication *)sender;

【建議】發(fā)送通知后再觸發(fā)delegate方法是一個(gè)例外:當(dāng)delegate方法的調(diào)用是為了告訴delegate對象,某個(gè)通知已經(jīng)被發(fā)送時(shí),這個(gè)delegate方法的參數(shù)應(yīng)該是通知對象,而非觸發(fā)delegate方法的對象。

- (void)windowDidChangeScreen:(NSNotification *)notification;

【建議】使用did或will這兩個(gè)情態(tài)動(dòng)詞通知delegate對象某件事已經(jīng)發(fā)生或?qū)⒁l(fā)生。

- (void)browserDidScroll:(NSBrowser *)sender;
- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window;

【建議】雖然我們可以在delegate方法中使用did和will來詢問delegate是否可以代替另一個(gè)對象做某件事情,但是使用should看起來更加完美。

- (BOOL)windowShouldClose:(id)sender;

(1.7) Private方法命名規(guī)范

大部分情況下,私有方法的命名和公有方法的命名規(guī)則是一樣的。然而,通常情況下應(yīng)該給私有方法添加一個(gè)前綴,目的是和公有方法區(qū)分開。盡管這樣,這種給私有方法加前綴的命名方式有可能引起一些奇怪的問題。問題就是:當(dāng)你從Cocoa framework(即Cocoa系統(tǒng)庫)中的某個(gè)類派生出來一個(gè)子類時(shí),你并不知道你的子類中定義的私有方法是否覆蓋了父類的私有方法,即有可能你自己在子類中實(shí)現(xiàn)的私有方法和父類中的某個(gè)私有方法同名。在運(yùn)行時(shí),這極有可能導(dǎo)致一些莫名其妙的問題,并且調(diào)試追蹤問題的難度也是相當(dāng)大。
Cocoa frameworks(Cocoa系統(tǒng)庫)中的私有方法通常以一個(gè)下劃線“ _ ”開頭,用于標(biāo)記這些方法是私有的(比如, _fooData ) 。不要問我為什么他們這么做,這大概就是Apple工程師的開發(fā)習(xí)慣?;谶@個(gè)事實(shí),提供以下兩條建議:
【必須】禁止使用下劃線“ _ “作為私有方法的開頭。Apple已經(jīng)預(yù)留這種私有方法的命名習(xí)慣。

【建議】如果你是要子類化Cocoa Frameworks中的一個(gè)非常龐大復(fù)雜的類(比如NSView或UIView),并且你想絕對的確保你自己的子類中的私有方法名和父類中的私有方法名不重復(fù)。你可以添加一個(gè)你自己的前綴作為私有方法的前綴,這個(gè)前綴應(yīng)該盡可能的獨(dú)特。也許這個(gè)前綴是基于你公司或者項(xiàng)目的縮寫,比如”XX_“。
盡管給私有方法增加前綴看起來和”方法存在于他們的類的命名空間中“這一之前的說法有些沖突,但此處的意圖是:為子類私有方法添加前綴僅僅是為了保證子類方法和父類方法名稱不沖突。

【必須】不要在參數(shù)的名稱中使用“pointer”或者"ptr"。應(yīng)該使用參數(shù)的類型來說明參數(shù)是否是一個(gè)指針。

【必須】不要使用一到兩個(gè)字符作為參數(shù)名。

【必須】不要對參數(shù)的每個(gè)單詞都縮寫。

【建議】如果調(diào)用某個(gè)方法是為了通知delegate某個(gè)事件"即將"發(fā)生或者"已經(jīng)"發(fā)生,則請?jiān)诜椒Q中使用“will”或者“did”這樣的助動(dòng)詞。例如:

- (void)applicationWillResignActive:(UIApplication *)application;
- (void)applicationDidEnterBackground:(UIApplication *)application;

【建議】如果調(diào)用某個(gè)方法是為了要求delegate代表其他對象執(zhí)行某件事情,我們應(yīng)該在方法中使用“should”這樣的情態(tài)動(dòng)詞。當(dāng)然,也可以在方法中使用“did”或者“will”這樣的字眼,但更傾向于前者。

- (BOOL)tableViewSholdScroll:(id)sender;

(1.8) Category命名規(guī)范

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

【必須】避免category中的方法覆蓋系統(tǒng)方法??梢允褂们熬Y來區(qū)分系統(tǒng)方法和category方法。但前綴不要僅僅使用下劃線”_“。

【建議】如果一個(gè)類比較復(fù)雜,建議使用category的方式組織代碼。具體可以參考UIView。

(1.9) Class命名規(guī)范

必須】class的名稱應(yīng)該由兩部分組成,前綴+名稱。即,class的名稱應(yīng)該包含一個(gè)前綴和一個(gè)名詞。

(1.10) Protocol命名規(guī)范

命名 說明
NSLocking OK
NSLock 不好,看起來像是一個(gè)類名

【建議】有時(shí)候protocol只是聲明了一堆相關(guān)方法,并不關(guān)聯(lián)class。這種不關(guān)聯(lián)class的protocol使用ing形式以和class區(qū)分開來。比如NSLocking而非NSLock。

命名 說明
NSLocking OK
NSLock 不好,看起來像是一個(gè)類名
命名 說明
UITableViewDelegate OK
NSObjectProtocol OK

【建議】如果proctocol不僅聲明了一堆相關(guān)方法,還關(guān)聯(lián)了某個(gè)class。這種關(guān)聯(lián)class的protocol的命名取決于關(guān)聯(lián)的class,然后再后面再加上protocol或delegate用于顯示的聲明這是一份協(xié)議。

命名 說明
UITableViewDelegate OK
NSObjectProtocol OK

(1.11) Notification命名規(guī)范

【建議】蘋果爸爸說:如果一個(gè)類聲明了delegate屬性,通常情況下,這個(gè)類的delegate對象可以通過實(shí)現(xiàn)的delegate方法收到大部分通知消息。那么,這些通知的名稱應(yīng)該反映出對應(yīng)的delegate方法。比如,application對象發(fā)送的NSApplicationDidBecomeActiveNotification通知和對應(yīng)的applicationDidBecomeActive:消息。其實(shí),這也算是命名的一致性要求。

【必須】notification的命名使用全局的NSString字符串進(jìn)行標(biāo)識(shí)。命名方式如下:
[Name of associated class] + [Did | Will] + [UniquePartOfName] + Notification

例如:

NSApplicationDidBecomeActiveNotification 
NSWindowDidMiniaturizeNotification 
NSTextViewDidChangeSelectionNotification 
NSColorPanelColorDidChangeNotification 

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

【必須】如果某個(gè)通知是為了告知外界某個(gè)事件"即將"發(fā)生或者"已經(jīng)"發(fā)生,則請?jiān)谕ㄖQ中使用will或者did這樣的助動(dòng)詞。例如:

UIKeyboardWillChangeFrameNotification; 
UIKeyboardDidChangeFrameNotification;

(1.12) Constant命名規(guī)范

Constants

(1.12.1) 枚舉常量

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

【建議】枚舉常量和typedef定義的枚舉類型的命名規(guī)范同函數(shù)的命名規(guī)范一致。(參考 Naming Functions

typedef enum _NSMatrixMode { 
    NSRadioModeMatrix = 0, 
    NSHighlightModeMatrix = 1, 
    NSListModeMatrix = 2, 
    NSTrackModeMatrix = 3 
} NSMatrixMode;

注意:上面枚舉typeof中的_NSMatrixMode是無用的。
我們可以像位掩碼(bit masks)一樣創(chuàng)建一個(gè)匿名枚舉,如下:

enum { NSBorderlessWindowMask = 0, 
    NSTitledWindowMask = 1 << 0, 
    NSClosableWindowMask = 1 << 1, 
    NSMiniaturizableWindowMask = 1 << 2, 
    NSResizableWindowMask = 1 << 3 
};
(1.12.2) 使用const關(guān)鍵字創(chuàng)建常量

【必須】使用const關(guān)鍵字創(chuàng)建浮點(diǎn)型常量。你也可以使用const來創(chuàng)建和其他常量不相關(guān)的整型常量。否則,請使用枚舉類型來創(chuàng)建。即,如果一個(gè)整型常量和其他常量不相關(guān),可以使用const來創(chuàng)建,否則,使用枚舉類型表示一組相關(guān)的整型常量。
以下例子聲明了const常量的格式:

const float NSLightGray;
(1.12.3) 其他常量類型

【必須】通常情況下,不要使用#define預(yù)處理命令(preprocessor command)創(chuàng)建常量。正如上面所說,對于整型常量,使用枚舉創(chuàng)建;對于浮點(diǎn)型常量,使用const修飾符創(chuàng)建。

【必須】有些符號需要使用大寫字母標(biāo)識(shí)。預(yù)處理器需要根據(jù)這個(gè)符號進(jìn)行計(jì)算以便決定是否要對某一塊代碼進(jìn)行處理。比如:

#ifdef DEBUG 

注意:那些編譯器定義的宏,左側(cè)和右側(cè)各有兩個(gè)下劃線。如下:

__MACH__

【必須】通知的名字和字典的key,應(yīng)該使用字符串常量來定義。使用字符串常量編譯器可以進(jìn)行檢查,這樣可以避免拼寫錯(cuò)誤。Cocoa 系統(tǒng)庫提供了許多字符串常量的例子,比如:

APPKIT_EXTERN NSString *NSPrintCopies;

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

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

(1.13) Exception命名規(guī)范

Notifications and Exceptions
上面已經(jīng)有一節(jié)介紹過通知的命名規(guī)范。異常和通知的命名遵循相似的規(guī)則,但又各有不同。

【必須】和Notification的命名規(guī)范一樣(可參考Notification命名規(guī)范一節(jié)),異常也是用全局的NSString字符串進(jìn)行標(biāo)識(shí)。命名方式如下:
[Prefix] + [UniquePartOfName] + Exception
相當(dāng)于異常由前綴、名稱中能夠標(biāo)識(shí)異常唯一性的那部分、Exception。如下:

NSColorListIOException 
NSColorListNotEditableException 
NSDraggingException 
NSFontUnavailableException 
NSIllegalSelectorException 

(二)編碼規(guī)范

(2.1) Initialize規(guī)范

Tips and Techniques for Framework Developers

  • (void)initialize類方法先于其他的方法調(diào)用。且initialize方法給我們提供了一個(gè)讓代碼once、lazy執(zhí)行的地方。initialize通常被用于設(shè)置class的版本號(參考 Versioning and Compatibility)。
    initialize方法的調(diào)用遵循繼承規(guī)則(所謂繼承規(guī)則,簡單來講是指:子類方法中可以調(diào)用到父類的同名方法,即使沒有調(diào)用[super xxx])。如果我們沒有實(shí)現(xiàn)initialize方法,運(yùn)行時(shí)初次調(diào)用這個(gè)類的時(shí)候,系統(tǒng)會(huì)沿著繼承鏈(類繼承體系),先后給繼承鏈上游中的每個(gè)超類發(fā)送一條initialize消息,直到某個(gè)超類實(shí)現(xiàn)了initlialize方法,才會(huì)停止向上調(diào)用。因此,在運(yùn)行時(shí),某個(gè)類的initialize方法可能會(huì)被調(diào)用多次(比如,如果一個(gè)子類沒有實(shí)現(xiàn)initialize方法)。
    比如:有三個(gè)類:SuperClass、SubClass和FinalClass。他們的繼承關(guān)系是這樣的FinalClass->SubClass->SuperClass,現(xiàn)只實(shí)現(xiàn)了SuperClass方法的initialize方法。
// SuperClass 
@implementation SuperClass 
+ (void)initialize { 
    NSLog(@"superClass initalize");
} 
@end 
// 初始化FinalClass - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent *)event {
    FinalClass *finalC = [FinalClass new];
} 
// 控制臺(tái)輸出結(jié)果 
2018-01-27 22:11:03.130365+0800 Demo[67162:11721965] superClass initalize 
2018-01-27 22:11:03.130722+0800 Demo[67162:11721965] superClass initalize 
2018-01-27 22:11:03.130815+0800 Demo[67162:11721965] superClass initalize

解釋:
因?yàn)镕inalClass繼承自SubClass,SubClass繼承自SuperClass。因?yàn)槔^承體系中只有SuperClass實(shí)現(xiàn)了initialize方法,導(dǎo)致初始化FinalClass這個(gè)子類時(shí),F(xiàn)inalClass會(huì)調(diào)用他的父類(SubClass)中的initialize方法。又因?yàn)樗?FinalClass)的父類(SubClass)也沒有實(shí)現(xiàn)initialize方法,又會(huì)繼續(xù)沿著繼承體系,向上游尋找,最后找到SubClass的父類(SuperClass)。因?yàn)镾uperClass實(shí)現(xiàn)了這個(gè)initialize方法,所以調(diào)用結(jié)束。至于為什么是連續(xù)調(diào)用了三次SuperClass的initialize方法。因?yàn)樽宇怓inalClass的初始化觸發(fā)了超類SubClass、SuperClass的初始化。所以初始化FinalClass時(shí),實(shí)際上使這三個(gè)類都得到了初始化的機(jī)會(huì),自然就會(huì)連續(xù)調(diào)用三次SuperClass的initialize方法。
還是上面那三個(gè)類,如果我們又給SubClass實(shí)現(xiàn)了initialize方法,那么控制臺(tái)將會(huì)輸出如下結(jié)果(至于為什么,前面已經(jīng)介紹過了,大家可以自己分析下):

2018-01-27 22:34:54.697952+0800 Load[67652:11780578] superClass initalize 
2018-01-27 22:34:54.698118+0800 Load[67652:11780578] subClass initialize 
2018-01-27 22:34:54.698472+0800 Load[67652:11780578] subClass initialize 

基于上面陳述的這些事實(shí),我們得出一個(gè)結(jié)論:
【必須】如果我們想要讓initialize方法僅僅被調(diào)用一次,那么需要借助于GCD的dispatch_once()。如下:

+ (void)initialize { 
    static dispatch_once_t onceToken = 0; 
    dispatch_once(&onceToken, ^{ 
        // the initializing code 
    }
}

【建議】如果我們想在繼承體系的某個(gè)指定的類的initialize方法中執(zhí)行一些初始化代碼,可以使用類型檢查和而非dispatch_once()。如下:

if (self == [NSFoo class]) {
     // the initializing code 
}

說了這么多,總而言之,由于任何子類都會(huì)調(diào)用父類的initialize方法,所以可能會(huì)導(dǎo)致某個(gè)父類的initialize方法會(huì)被調(diào)用多次,為了避免這種情況,我們可以使用類型判等或dispatch_once()這兩種方式,以保證initialize中的代碼不會(huì)被無辜調(diào)用。

initialize是由系統(tǒng)自動(dòng)調(diào)用的方法,我們不應(yīng)該顯示或手動(dòng)調(diào)用initialize方法。如果我們要觸發(fā)某個(gè)類的初始化行為,應(yīng)該調(diào)用這個(gè)類的一些無害的方法。比如:

[NSImage self];

(2.2 )Init方法規(guī)范

Objective-C有designated Initializers和secondary Initializers的概念。designated Initializers叫做指定初始化方法?!禘ffective Objective-C 2.0 編寫高質(zhì)量iOS 與 OS X代碼的52個(gè)有效方法》中將designated Initializers翻譯為”全能初始化方法“。designated Initializers方法是指類中為對象提供必要信息以便其能完成工作的初始化方法。一個(gè)類可以有一個(gè)或者多個(gè)designated Initializers。但是要保證所有的其他secondary initializers都要調(diào)用designated Initializers。即:只有designated Initializers才會(huì)存儲(chǔ)對象的信息。這樣的好處是:當(dāng)這個(gè)類底層的某些數(shù)據(jù)存儲(chǔ)機(jī)制發(fā)生變化時(shí)(可能是一些property的變更),只需要修改這個(gè)designated Initializers內(nèi)部的代碼即可。無需改動(dòng)其他secondary Initializers初始化方法的代碼。

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

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

【必須】如果子類的designated初始化方法與超類的designated初始化方法不同,則子類應(yīng)該覆寫超類的designated初始化方法。(因?yàn)殚_發(fā)者很有可能直接調(diào)用超類的某個(gè)designated方法來初始化一個(gè)子類對象,這樣也是合情合理的,但使用超類的方法初始化子類,可能會(huì)導(dǎo)致子類在初始化時(shí)缺失一些必要信息)。

【必須】如果超類的某個(gè)初始化方法不適用于子類,則子類應(yīng)該覆寫這個(gè)超類的方法,并在其中拋出異常。

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

// 超類 @interface ParentObject : NSObject 
@end 
@implementation ParentObject 
    //designated initializer 
    - (instancetype)initWithURL:(NSString*)url title:(NSString*)title { 
        if (self = [super init]) {
            _url = [url copy];
            _title = [title copy];
        } 
        return self;
    } //secondary initializer 
    - (instancetype)initWithURL:(NSString*)url { 
        return [self initWithURL:url title:nil];
    } 
    @end 
// 子類 
@interface ChildObject : ParentObject 
@end 
    @implementation ChildObject 
    //designated initializer 
    - (instancetype)initWithURL:(NSString*)url title:(NSString*)title { 
        //在designated intializer中調(diào)用 secondary initializer,錯(cuò)誤的 
        if (self = [super initWithURL:url]) {

        } return self;
    } 
    @end 
@implementation ViewController 
    - (void)viewDidLoad {
        [super viewDidLoad]; 
        // 這里會(huì)死循環(huán) 
        ChildObject* child = [[ChildObject alloc] initWithURL:@"url" title:@"title"];
    } 
    @end 

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

(2.3) Init error

一個(gè)好的初始化方法應(yīng)該具備以下幾個(gè)方面,在初始化階段就能夠發(fā)現(xiàn)錯(cuò)誤并給予處理,也就是初始化方法應(yīng)該具備一些必要的容錯(cuò)功能。

【必須】調(diào)用父類的designated初始化方法初始化本類的對象。

【必須】校驗(yàn)父類designated初始化方法返回的對象是否為nil。

【建議】如果初始化當(dāng)前對象的時(shí)候發(fā)生了錯(cuò)誤,應(yīng)該給予對應(yīng)的處理:釋放對象,并返回nil。
以下實(shí)例列舉類初始化階段可能會(huì)存在的錯(cuò)誤:

- (id)init {    self = [super init];  // Call a designated initializer here.
    if (self != nil) {        
        // Initialize object  ...
        if (someError) {
            [self release];
            self = nil;
        }
    }    
    return self;
}

(2.4) dealloc規(guī)范

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

【建議】dealloc 方法應(yīng)該放在實(shí)現(xiàn)文件的最上面,并且剛好在 @synthesize 和 @dynamic 語句的后面。在任何類中,init 都應(yīng)該直接放在 dealloc 方法的下面。

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

-(void)dealloc{
    [self unsafeMethod:self];    
    // 因?yàn)楫?dāng)前已經(jīng)在self這個(gè)指針?biāo)赶虻膶ο蟮匿N毀階段,銷毀self所指向的對象已經(jīng)在所難免。如果在unsafeMethod:中把self放到了autorelease poll中,那么self會(huì)被retain住,計(jì)劃下個(gè)runloop周期在進(jìn)行銷毀。但是dealloc運(yùn)行結(jié)束后,self所指向的對象的內(nèi)存空間就直接被回收了,但是self這個(gè)指針還沒有銷毀(即沒有被置為nil),導(dǎo)致self變成了一個(gè)名副其實(shí)的野指針。
    // 到了下一個(gè)runloop周期,因?yàn)閟elf所指向的對象已經(jīng)被銷毀,會(huì)因?yàn)榉欠ㄔL問而造成crash問題。
}

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

(2.5) Block規(guī)范

【必須】調(diào)用block時(shí)需要對block判空。

【必須】注意block潛在的引用循環(huán)。

(2.6) Notification規(guī)范

前面在命名規(guī)范一章中已經(jīng)介紹了通知的命名規(guī)范,這里解釋的是通知的使用規(guī)范。
通知作為觀察者模式的一個(gè)落地產(chǎn)物,在開發(fā)中能夠?qū)崿F(xiàn)一對多的通信。所有可以使用delegate和block實(shí)現(xiàn)的通信和傳值,都可以使用通知實(shí)現(xiàn)。正因通知如此靈活,我們更應(yīng)該弄清楚通知適合使用的場景,避免把通知和delegate以及block等進(jìn)行混淆。
通知是一把雙刃劍,讓你歡喜讓你憂。開發(fā)中,當(dāng)你走投無路將要崩潰時(shí),可以考慮使用通知;而當(dāng)你頻繁使用通知時(shí),同樣會(huì)讓你崩潰到走投無路。所以,在每個(gè)應(yīng)用中,我們應(yīng)該時(shí)刻留意并控制通知的數(shù)量,避免通知滿天飛的現(xiàn)象。
曾經(jīng)有一個(gè)項(xiàng)目擺在我面前,我卻無法珍惜,因?yàn)橥ㄖ嗔?,幾乎有代碼的地方就有通知。如果現(xiàn)在同樣有一個(gè)充滿通知的項(xiàng)目擺在我面前,我知道是時(shí)候該優(yōu)化它了。

【必須】基于以上的陳述,當(dāng)我們使用通知時(shí),必須要思考,有沒有更好的辦法來代替這個(gè)通知。禁止遇到問題就想到通知,把通知作為備選項(xiàng)而非首選項(xiàng)。

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

【必須】NSNotificationCenter在iOS8及更老的系統(tǒng)有一個(gè)多線程bug,selector執(zhí)行到一半可能會(huì)因?yàn)?code>self的銷毀而引起crash,解決的方案是在selector中使用weak_strong_dance。如下:

- (void)onMultiThreadNotificationTrigged:(NSNotification *)notify {
    __weak typeof(self) wself = self; __strong typeof(self) sself = wself; 
    if (!sself) { return; }
    [self doSomething]; 
}

【必須】在多線程應(yīng)用中,Notification在哪個(gè)線程中post,就在哪個(gè)線程中被轉(zhuǎn)發(fā),而不一定是在注冊觀察者的那個(gè)線程中。如果post消息不在主線程,而接受消息的回調(diào)里做了UI操作,需要讓其在主線程執(zhí)行。
說明:每個(gè)進(jìn)程都會(huì)創(chuàng)建一個(gè)NotificationCenter,這個(gè)center通過NSNotificationCenter defaultCenter獲取,當(dāng)然也可以自己創(chuàng)建一個(gè)center。
NoticiationCenter是以同步(非異步,當(dāng)前線程,會(huì)等待,會(huì)阻塞)的方式發(fā)送請求。即,當(dāng)post通知時(shí),center會(huì)一直等待所有的observer都收到并且處理了通知才會(huì)返回到poster。如果需要異步發(fā)送通知,請使用notificationQueue,在一個(gè)多線程的應(yīng)用中,通知會(huì)發(fā)送到所有的線程中。

(2.7) UI規(guī)范

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

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

【必須】UITableView使用self-sizing實(shí)現(xiàn)不等高cell時(shí),請?jiān)?code>- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;中給cell設(shè)置數(shù)據(jù)。不要在- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;方法中給cell設(shè)置數(shù)據(jù)。

【建議】當(dāng)訪問一個(gè)CGRectxy,widthheight時(shí),應(yīng)該使用CGGeometry函數(shù)代替直接訪問結(jié)構(gòu)體成員。蘋果的CGGeometry參考中說到:

All functions described in this reference that take CGRect data structures as inputs implicitly standardize those rectangles before calculating their results. For this reason, your applications should avoid directly reading and writing the data stored in the CGRect data structure. Instead, use the functions described here to manipulate rectangles and to retrieve their characteristics.

因此,推薦的寫法是這樣的:

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] 會(huì)block住當(dāng)前線程,知道所有的內(nèi)容都寫進(jìn)磁盤,如果內(nèi)容過多,重復(fù)調(diào)用的話會(huì)嚴(yán)重影響性能。

【建議】一些經(jīng)常被使用的文件建議做好緩存。避免重復(fù)的IO操作。建議只有在合適的時(shí)候再進(jìn)行持久化操作。

(2.9) Collection規(guī)范

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

// 可能崩潰 NSObject *obj = somOjbcetMaybeNil;
NSMutableArray *arrM = [NSMutableArray arrayWithObject:obj]; 

// 崩潰信息: 
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[0]’ 

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

【必須】注意在多線程環(huán)境下訪問可變集合對象的問題,必要時(shí)應(yīng)該加鎖保護(hù)。不可變集合(比如NSArray)類默認(rèn)是線程安全的,而可變集合類(比如NSMutableArray)不是線程安全的。

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

// 正確寫法
- (void)checkAllValidItems{
NSArray *array = [array copy];
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
//do something using obj
}]; }
// 錯(cuò)誤寫法
- (void)checkAllValidItems{
[self.allItems enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    //do something using obj
    // 如果在enumerate過程中,其他線程對allItems這個(gè)可變集合進(jìn)行了變更操作,這里就有可能引發(fā)crash
}]; }

【必須】注意使用enumerateObjectsUsingBlock遍歷集合對象中的對象時(shí),關(guān)鍵字return的作用域。block中的return代表的是使當(dāng)前的block返回,而非使當(dāng)前的整個(gè)函數(shù)體返回。以下使用NSArray舉例,其他集合類型同理。如下:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSArray *array = [NSArray arrayWithObject:@"1"];
    [array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        // excute some code...
        return;
    }];
    // 依然會(huì)執(zhí)行到這里
    NSLog(@"fall through");
}
// 執(zhí)行結(jié)果:
// fall through

當(dāng)然,兩個(gè)enumerateObjectsUsingBlock嵌套,如果僅在最內(nèi)層的block中return,外層block的代碼還是會(huì)被執(zhí)行。如下:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSArray *arr1 = [NSArray arrayWithObject:@"1"];
    NSArray *arr2 = [NSArray arrayWithObject:@"2"];
    [arr2 enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        [arr1 enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            // do something
            return;
        }];
         
        NSLog(@"fall through");
    }];
     
    NSLog(@"fall through");
}
// 執(zhí)行結(jié)果:
// fall through
// fall through

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

【必須】禁止返回mutable對象,禁止mutable對象作為入?yún)鬟f。

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

(2.10) 分支語句規(guī)范

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

// 建議
if (!error) {
    return success;
}
// 不建議
if (!error) 
    return success;
     
if (!error)  return success;

【必須】多于3個(gè)邏輯表達(dá)式必須用參數(shù)分割成多個(gè)有意義的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];
}

【建議】對于條件語句的真假,因?yàn)?nil 解析為 NO,所以沒有必要在條件中與它進(jìn)行比較。永遠(yuǎn)不要直接和 YES 和 NO進(jìn)行比較,因?yàn)?YES 被定義為 1,而 BOOL 可以多達(dá) 8 位。

// 建議
if (isAwesome)
if (![someObject boolValue])
// 禁止這樣做
if ([someObject boolValue] == NO) { }
if (isAwesome == YES) { }

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

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

(2.11) 對象判等規(guī)范

isEqual:方法允許我們傳入任意類型的對象作為參數(shù),如果參數(shù)類型和receiver(方法調(diào)用者)類型不一致,會(huì)返回NO。而isEqualToString:isEqualToArray:這兩個(gè)方法會(huì)假設(shè)參數(shù)類型和receiver類型一致,也就是說,這兩個(gè)方法不會(huì)對參數(shù)進(jìn)行類型檢查。因此這兩個(gè)方法性能更好但不安全。如果我們是從外部數(shù)據(jù)源(比如info.plist或preferences)獲取的數(shù)據(jù),那么推薦使用isEqual:,因?yàn)檫@樣更安全。如果我們知道參數(shù)的確切類型,那么可以使用類似于isEqualToString:這樣的方法,因?yàn)樾阅芨谩?/p>

(2.12) 懶加載規(guī)范

懶加載適合的場景:

  • 一個(gè)對象的創(chuàng)建依賴于其他對象。
  • 一個(gè)對象在整個(gè)app過程中,可能被使用,也可能不被使用。
  • 一個(gè)對象的創(chuàng)建需要經(jīng)過大量的計(jì)算或者比較消耗性能。除以上三條之外,請不要使用懶加載。

【建議】懶加載本質(zhì)上就是延遲初始化某個(gè)對象,所以,懶加載僅僅是初始化一個(gè)對象,然后對這個(gè)對象的屬性賦值。懶加載中不應(yīng)該有其他的不必要的邏輯性的代碼,如果有,請把那些邏輯性代碼放到合適的地方。

【必須】不要濫用懶加載,只對那些真正需要懶加載的對象采用懶加載。

【必須】如果一個(gè)對象在懶加載后,某些場景下又被設(shè)置為nil。我們很難保證這個(gè)懶加載不被再次觸發(fā)。

(2.13) 多線程規(guī)范

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

【建議】僅當(dāng)必須保證順序執(zhí)行時(shí)才使用dispatch_sync,否則容易出現(xiàn)死鎖,應(yīng)避免使用,可使用dispatch_async。

- (void)viewDidLoad {
   [super viewDidLoad];
   // 錯(cuò)誤。出現(xiàn)死鎖,報(bào)錯(cuò):EXC_BAD_INSTRUCTION。原因:在主隊(duì)列中同步的添加一個(gè)block到主隊(duì)列中
   dispatch_queue_t mainQueue = dispatch_get_main_queue();
   dispatch_block_t block = ^() {
       NSLog(@"%@", [NSThread currentThread]);
   };
   dispatch_sync(mainQueue, block);
}
- (void)viewDidLoad {
   [super viewDidLoad];
   // 正確。異步執(zhí)行。雖然還是把任務(wù)加到了主隊(duì)列由主線程來執(zhí)行,但因?yàn)槭钱惒剑藭r(shí)主隊(duì)列后面的任務(wù)不依賴于前面的任務(wù)。
   dispatch_queue_t mainQueue = dispatch_get_main_queue();
   dispatch_block_t block = ^() {
       NSLog(@"%@", [NSThread currentThread]);
   };
   dispatch_async(mainQueue, block);
}
// 打印結(jié)果:
// {number = 1, name = main}

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

【必須】在主線程中禁止進(jìn)行同步網(wǎng)絡(luò)資源讀取,使用NSURLSession進(jìn)行異步獲取。當(dāng)然,你可以在子線程同步獲取網(wǎng)絡(luò)資源,但還是上面的那一條建議:避免使用dispatch_sync,盡量使用dispatch_async。因?yàn)樗梨i不一定只發(fā)生在主線程。

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

【必須】對剪貼板的讀取必須要放在異步線程處理,最新Mac和iOS里的剪貼板共享功能會(huì)導(dǎo)致有可能需要讀取大量的內(nèi)容,導(dǎo)致讀取線程被長時(shí)間阻塞。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
   UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; 
   if (pasteboard.string.length > 0) {//這個(gè)方法會(huì)阻塞線程
      NSString *text = [pasteboard.string copy];
      [pasteboard setValue:@"" forPasteboardType:UIPasteboardNameGeneral];
      if (text == nil || [text isEqualToString:@""]) {
          return ;
      }
      dispatch_async(dispatch_get_main_queue(), ^{
          [self processShareCode:text];
      });
   }
});

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

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

【建議】請慎重使用單例,避免產(chǎn)生不必要的常駐內(nèi)存。
說明:我們不僅應(yīng)該知道單例的特點(diǎn)和優(yōu)勢,也必須要弄明白單例適合的場景。UIApplication、access database 、request network、access userInfo這類全局僅存在一份的對象或者需要多線程訪問的對象,可以使用單例。不要僅僅為了訪問方便就使用單例。

【建議】單例初始化方法中盡量保證單一職責(zé),尤其不要進(jìn)行其他單例的調(diào)用。極端情況下,兩個(gè)單例對象在各自的單例初始化方法中調(diào)用,會(huì)造成死鎖。

【必須】在dealloc方法中,禁止將self作為參數(shù)傳遞出去,如果selfretain住,到下個(gè)runloop周期再釋放,則會(huì)造成多次釋放crash。這一點(diǎn)在dealloc一節(jié)中有說明。

【建議】除非你清除的知道自己在做什么。否則不建議將UIView類的對象加入到NSArray、NSDictionary、NSSet中。如有需要可以添加到NSMapTableNSHashTable。因?yàn)?code>NSArray、NSDictionary、NSSet會(huì)對加入的對象做strong引用(即使你把加入的對象進(jìn)行了weak)。而NSMapTable、NSHashTable會(huì)對加入的對象做weak引用。
說明:簡單的說,NSHashTable相當(dāng)于weakNSMutableArray;NSMapTable相當(dāng)于weakNSMutableDictionary.

// 錯(cuò)誤的例子:
@implementation WSObject
- (void)dealloc {
    NSLog(@"dealloc");
}
@end
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    WSObject *object = [WSObject new];
    // 即使對object進(jìn)行了weak弱化,數(shù)組也會(huì)強(qiáng)引用這個(gè)object對象。dealloc方法不會(huì)被執(zhí)行。
    __weak typeof(object) weakObject = object;
    [self.arrM addObject:weakObject];
     
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"count = %ld",self.arrM.count);
    });
}
// 打印結(jié)果:
// count = 1
// 正確的例子:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    WSObject *object = [WSObject new];
    NSHashTable *hashTable = [NSHashTable weakObjectsHashTable];
    [hashTable addObject:object];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"count = %ld",hashTable.count);
    });
}
// 打印結(jié)果:
// dealloc
// count = 1

你可能對上面的例子有所疑惑,object已經(jīng)釋放了,但是控制臺(tái)仍然輸出 hashTable.count == 1。但是請相信我,此時(shí)存在于hashTable中的那個(gè)object已經(jīng)變成了nil。不信你繼續(xù)看下面的例子:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    WSObject *object = [WSObject new];
    NSHashTable *hashTable = [NSHashTable weakObjectsHashTable];
    [hashTable addObject:object];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"count = %ld",hashTable.count);
         
        if (hashTable && hashTable.count) {
            WSObject *object = [hashTable anyObject];
            NSLog(@"object = %@",[object self]);
        }
    });
}
// 打印結(jié)果:
2017-07-04 22:19:10.952139+0800 tst[46834:4305636] dealloc
2017-07-04 22:19:13.149903+0800 tst[46834:4305636] count = 1
2017-07-04 22:20:55.234522+0800 tst[46834:4305636] object = (null)

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

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

使用performSelector:withObject:afterDelay:cancelPreviousPerformRequestsWithTarget組合的時(shí)候要小心:
afterDelay會(huì)增加引用計(jì)數(shù),而cancel會(huì)對引用計(jì)數(shù)減一
如果receiver在引用計(jì)數(shù)器為1的時(shí)候,調(diào)用cancel會(huì)立即回收receiver。后續(xù)再次調(diào)用receive的方法就會(huì)crash。所以我們需要使用weakSelf并判空。如下:

__weak typeof(self) weakSelf = self;
[NSObject cancelPreviousPerformRequestsWithTarget:self];
if (!weakSelf) { 
  // NSLog(@"self dealloc");
  return;
 }
[self doOther];

(2.16) 注釋規(guī)范

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

(2.17) 類的設(shè)計(jì)規(guī)范

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

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

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

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

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

(2.18) 代碼組織規(guī)范

#pragma mark - Lifecycle
- (instancetype)init {}
- (void)dealloc {}
- (void)viewDidLoad {}
- (void)viewWillAppear:(BOOL)animated {}
- (void)didReceiveMemoryWarning {}
#pragma mark - Custom Accessors
- (void)setCustomProperty:(id)value {}
- (id)customProperty {}
#pragma mark - IBActions
- (IBAction)submitData:(id)sender {}
#pragma mark - Public
- (void)publicMethod {}
#pragma mark - Private
- (void)privateMethod {}
#pragma mark - UITextFieldDelegate
#pragma mark - UITableViewDataSource
#pragma mark - UITableViewDelegate
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone {}
#pragma mark - NSObject
- (NSString *)description {}

【建議】以上只是提供了組織代碼的一種思路,如果有其他更好的組織方式,也不是不可以。

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

【必須】為了避免文件雜亂,物理文件應(yīng)該保持和 Xcode 項(xiàng)目文件同步。Xcode 創(chuàng)建的任何組(group)都必須在文件系統(tǒng)有相應(yīng)的映射。為了更清晰,代碼不僅應(yīng)該按照類型進(jìn)行分組,也可以根據(jù)業(yè)務(wù)功能進(jìn)行分組。

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

【建議】盡可能一直打開 target Build Settings 中 "Treat Warnings as Errors" 以及一些額外的警告。如果你需要忽略指定的警告,使用 Clang 的編譯特性

轉(zhuǎn)載:文/VV木公子(簡書作者)http://www.itdecent.cn/p/c818c00e0690

轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),并注明出處。

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

相關(guān)閱讀更多精彩內(nèi)容

  • 前言 說是前言,其實(shí)也是本文誕生的目的。隨著公司業(yè)務(wù)的不斷增加,功能的快速迭代,app的業(yè)務(wù)線越來越多,代碼體積變...
    夢翔_d674閱讀 1,555評論 0 2
  • 前言 說是前言,其實(shí)也是本文誕生的目的。隨著公司業(yè)務(wù)的不斷增加,功能的快速迭代,app的業(yè)務(wù)線越來越多,代碼體積變...
    Yealink閱讀 5,604評論 0 13
  • 約定 在我看來,開發(fā)規(guī)范像是一條可供參考的標(biāo)準(zhǔn)線。不同開發(fā)者可以根據(jù)這條標(biāo)準(zhǔn)線來規(guī)范自己的開發(fā)行為,尤其是在大的項(xiàng)...
    xxzsxxzs閱讀 701評論 1 0
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 30,203評論 8 265
  • 90天五大目標(biāo): 1.養(yǎng)成5點(diǎn)起床的習(xí)慣 2.堅(jiān)持每天聽時(shí)間管理、營銷計(jì)劃并記錄。 3.學(xué)習(xí)簡愛跑步法,堅(jiān)持一周跑...
    莫丹陽閱讀 155評論 0 0

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