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

前言

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

約定

在我看來唯竹,開發(fā)規(guī)范像是一條可供參考的標(biāo)準(zhǔn)線。不同開發(fā)者可以根據(jù)這條標(biāo)準(zhǔn)線來規(guī)范自己的開發(fā)行為苦丁,尤其是在大的項目中浸颓,開發(fā)規(guī)范可以約束不同開發(fā)者的開發(fā)風(fēng)格,使項目從細節(jié)到整體上都能達到風(fēng)格統(tǒng)一旺拉,利于維護产上。
本文的開發(fā)規(guī)范由很多item組成,不同的item描述了不同的問題蛾狗。每一個item就是一條具體的開發(fā)規(guī)范晋涣,違反不同的開發(fā)規(guī)范,也會引起不同嚴(yán)重程度的后果沉桌。就像法律和道德的差異一樣谢鹊,我們必須遵守法律,不然可能帶來損人不利己的嚴(yán)重后果留凭,但有些人雖然沒有觸犯法律佃扼,卻違背了道德,雖然暫時沒有產(chǎn)生嚴(yán)重的后果蔼夜,長此以往兼耀,也會形成一種壞的風(fēng)氣。所以,無論法律和道德瘤运,我們都該鞭策自己成為優(yōu)秀的人窍霞,而不該止步于一個合格的人。同理尽超,開發(fā)規(guī)范也是如此官撼,我們必須遵守那些必須要遵守的開發(fā)規(guī)范,提倡遵守那些建議你遵守的開發(fā)規(guī)范似谁。所以傲绣,根據(jù)約束力度,我們把開發(fā)規(guī)范暫時劃分成兩個等級巩踏,分別是【必須】秃诵、【建議】。

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

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

    1. 通用命名規(guī)范(講述命名的一些通用規(guī)范)
    1. 縮寫規(guī)范(講述常見的縮寫以及縮寫規(guī)范)
    1. Method命名規(guī)范(講述方法命名的具體規(guī)范)
    1. Accessor命名規(guī)范(講述set和get方法的命名規(guī)范)
    1. Parameter命名規(guī)范(講述參數(shù)命名規(guī)范)
    1. Delegate方法命名規(guī)范(講述delegate方法的命名規(guī)范)
    1. Private方法命名規(guī)范(講述私有方法的命名規(guī)范)
    1. Category命名規(guī)范(講述分類的命名規(guī)范)
    1. Class命名規(guī)范(講述類命名規(guī)范)
    1. Protocol命名規(guī)范(講述協(xié)議的命名規(guī)范)
    1. Notification命名規(guī)范(講述通知的命名規(guī)范)
    1. Constant命名規(guī)范(講述枚舉常量以及const常量的命名規(guī)范)
    1. Exception命名規(guī)范(講述異常的命名規(guī)范)

(二)編碼規(guī)范

    1. Initialize方法(講述類的initialize方法的使用規(guī)范)
    1. Init方法(講述初始化方法的設(shè)計規(guī)范包括designated init方法和secondary init方法)
    1. Init error(講述init方法初始化對象失敗時的錯誤處理)
    1. Dealloc規(guī)范(講述dealloc方法的使用規(guī)范)
    1. Block規(guī)范(講述block的使用規(guī)范)
    1. Notification規(guī)范(講述通知的使用規(guī)范)
    1. UI規(guī)范(講述開發(fā)UI時的一些規(guī)范)
    1. IO規(guī)范(講述讀寫文件時的一些注意事項)
    1. Collection規(guī)范(講述集合類型的使用規(guī)范)
    1. 分支語句規(guī)范(講述常用的分支語句if侯嘀、switch語句的編碼規(guī)范)
    1. 對象判等規(guī)范(講述常用的判定對象等同性的方法使用規(guī)范)
    1. 懶加載規(guī)范(講述懶加載的使用規(guī)范)
    1. 多線程規(guī)范(講述多線程環(huán)境下的一些編碼規(guī)范)
    1. 內(nèi)存管理規(guī)范(講述編碼過程中常見的內(nèi)存管理注意點)
    1. 延遲調(diào)用規(guī)范(講述使用延遲方法時注意事項)
    1. 注釋規(guī)范(講述編碼中注釋的使用規(guī)范)
    1. 類的設(shè)計規(guī)范(講述類的設(shè)計規(guī)范)
    1. 代碼組織規(guī)范(講述類中的代碼組織規(guī)范)
    1. 工程結(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)然也有例外,下面我們會針對于每一種情況一一列舉。
【必須】自我描述性把沼。屬性/函數(shù)/參數(shù)/變量/常量/宏 的命名必須具有自我描述性啊易。杜絕中文拼音、過度縮寫饮睬、或者無意義的命名方式租谈。

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

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

掩碼常量呻逆、通知除外:

命名 說明
NSUnderlineByWordMask 規(guī)范的寫法
NSTableViewColumnDidMoveNotification 規(guī)范的寫法

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

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

// count同時定義在NSDictionary邦蜜、NSArray依鸥、NSSet這三個集合類中。且這三個集合類中的count屬性都代表同一個意思悼沈,即集合中對象的個數(shù)贱迟。
@property (readonly) NSUInteger count;

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

命名 說明
insertObject:atIndex: 規(guī)范的寫法
insert:at: 不清晰壤靶,插入什么缚俏?at代表什么?
removeObjectAtIndex: 規(guī)范的寫法
removeObject: 規(guī)范的寫法,因為參數(shù)指明了要移除一個對象
remove: 不清晰忧换,移除什么恬惯?

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

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

(1.2) 縮寫規(guī)范

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

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

我們也可以使用計算機行業(yè)通用的縮寫。包括但不限于HTML保屯、URL手负、RTF、HTTP姑尺、TIFF竟终、JPG、PNG切蟋、GIF统捶、LZW、ROM柄粹、RGB喘鸟、CMYK、MIDI驻右、FTP什黑。

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

【必須】方法名也要采用小寫字母開頭的駝峰命名方式。如果方法名以一個中所周知的大寫縮略詞開頭(比如HTTP)堪夭,該規(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)該相同或者類似赤屋。

// 該方法同時定義在NSView立镶、NSControl、NSCell這三個類里面类早。
- (NSInteger)tag;
// 該屬性同時定義在NSDcitionary和NSArray中媚媒。
@property (readonly) NSUInteger count;

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

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

【必須】蘋果爸爸還說:如果方法代表對象接收的動作欣范,那么方法一動詞開頭变泄。但不要使用“do”或者"does"作為方法名稱的一部分令哟,因為這些助動詞不能為方法名稱增加太多的意義,反而讓方法看起來更加臃腫妨蛹。同時屏富,也請不要在動詞前面使用副詞或者形容詞。

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

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

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

// 通過傳入指針,來獲得多個值
- (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)鍵字飘千,除非你能保證每個人都能意會到你的精神堂鲜。

命名 說明
- (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)該單獨的提供一個方法霉旗,并在新的方法后面添加上必要的關(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ù)的增加抛虫,這將會帶來一系列的問題。

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

【建議】如果方法描述了兩個獨立的動作简僧,可以使用“and”連接起來建椰。

命名 說明
- (BOOL)openFile:(NSString *)fullPath withApplication:(NSString *)appName andDeactivate:(BOOL)flag; OK (NSWorkspace. )

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

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

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

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

【建議】如果屬性表示一個形容詞,推薦格式如下:

- (BOOL)isAdjective;
- (void)setAdjective:(BOOL)flag;

例如:
- (BOOL)isEditable;
- (void)setEditable:(BOOL)flag;

【建議】如果屬性是一個動詞啦逆,動詞使用一般現(xiàn)在時伞矩。推薦格式如下:

- (BOOL)verbObject;
- (void)setVerbObject:(BOOL)flag;

例如:
- (BOOL)showsAlpha;
- (void)setShowsAlpha:(BOOL)flag;

【必須】不要把動詞的過去分詞形式當(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)動詞(can夏志、should乃坤、will等)明確方法意義,但不要使用do沟蔑、does這類無意義的情態(tài)動詞湿诊。

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

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

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

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

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

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

delegate methods 又叫做delegation methods错沽,如果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;

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

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

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

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

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

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

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

- (BOOL)windowShouldClose:(id)sender;

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

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

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

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

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

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

【建議】如果調(diào)用某個方法是為了通知delegate某個事件"即將"發(fā)生或者"已經(jīng)"發(fā)生芜飘,則請在方法名稱中使用“will”或者“did”這樣的助動詞。例如:

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

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

- (BOOL)tableViewSholdScroll:(id)sender;

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

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

【必須】避免category中的方法覆蓋系統(tǒng)方法馆纳∈迹可以使用前綴來區(qū)分系統(tǒng)方法和category方法。但前綴不要僅僅使用下劃線”_“鲁驶。

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

1.9 Class命名規(guī)范

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

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

命名 說明
NSLocking OK
NSLock 不好膛堤,看起來像是一個類名

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

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

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

命名 說明
UITableViewDelegate OK
NSObjectProtocol OK

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

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

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

NSApplicationDidBecomeActiveNotification

NSWindowDidMiniaturizeNotification

NSTextViewDidChangeSelectionNotification

NSColorPanelColorDidChangeNotification

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

【必須】如果某個通知是為了告知外界某個事件"即將"發(fā)生或者"已經(jīng)"發(fā)生坐梯,則請在通知名稱中使用“will”或者“did”這樣的助動詞务蝠。例如:

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)建一個匿名枚舉,如下:

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)建浮點型常量践瓷。你也可以使用const來創(chuàng)建和其他常量不相關(guān)的整型常量院喜。否則,請使用枚舉類型來創(chuàng)建晕翠。即喷舀,如果一個整型常量和其他常量不相關(guān),可以使用const來創(chuàng)建淋肾,否則硫麻,使用枚舉類型表示一組相關(guān)的整型常量。
以下例子聲明了const常量的格式:

const float NSLightGray;

1.12.3 其他常量類型

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

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

#ifdef DEBUG

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

__MACH__

【必須】通知的名字和字典的key,應(yīng)該使用字符串常量來定義。使用字符串常量編譯器可以進行檢查绪囱,這樣可以避免拼寫錯誤测蹲。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字符串進行標(biāo)識媒咳。命名方式如下:
[Prefix] + [UniquePartOfName] + Exception
相當(dāng)于異常由前綴、名稱中能夠標(biāo)識異常唯一性的那部分种远、Exception涩澡。如下:

NSColorListIOException

NSColorListNotEditableException

NSDraggingException

NSFontUnavailableException

NSIllegalSelectorException

(二)編碼規(guī)范

(2.1) Initialize規(guī)范

Tips and Techniques for Framework Developers

  • (void)initialize類方法先于其他的方法調(diào)用。且initialize方法給我們提供了一個讓代碼once坠敷、lazy執(zhí)行的地方妙同。initialize通常被用于設(shè)置class的版本號(參考 Versioning and Compatibility)。
    initialize方法的調(diào)用遵循繼承規(guī)則(所謂繼承規(guī)則膝迎,簡單來講是指:子類方法中可以調(diào)用到父類的同名方法粥帚,即使沒有調(diào)用[super xxx])。如果我們沒有實現(xiàn)initialize方法限次,運行時初次調(diào)用這個類的時候芒涡,系統(tǒng)會沿著繼承鏈(類繼承體系),先后給繼承鏈上游中的每個超類發(fā)送一條initialize消息卖漫,直到某個超類實現(xiàn)了initlialize方法费尽,才會停止向上調(diào)用。因此羊始,在運行時旱幼,某個類的initialize方法可能會被調(diào)用多次(比如,如果一個子類沒有實現(xiàn)initialize方法)突委。
    比如:有三個類:SuperClass速警、SubClass和FinalClass。他們的繼承關(guān)系是這樣的FinalClass->SubClass->SuperClass鸯两,現(xiàn)只實現(xiàn)了SuperClass方法的initialize方法闷旧。
// SuperClass
@implementation SuperClass
+ (void)initialize {
    NSLog(@"superClass initalize");
}
@end
// 初始化FinalClass
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    FinalClass *finalC = [FinalClass new];
}
// 控制臺輸出結(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

解釋:
因為FinalClass繼承自SubClass,SubClass繼承自SuperClass钧唐。因為繼承體系中只有SuperClass實現(xiàn)了initialize方法忙灼,導(dǎo)致初始化FinalClass這個子類時,F(xiàn)inalClass會調(diào)用他的父類(SubClass)中的initialize方法。又因為他(FinalClass)的父類(SubClass)也沒有實現(xiàn)initialize方法该园,又會繼續(xù)沿著繼承體系酸舍,向上游尋找,最后找到SubClass的父類(SuperClass)里初。因為SuperClass實現(xiàn)了這個initialize方法啃勉,所以調(diào)用結(jié)束。至于為什么是連續(xù)調(diào)用了三次SuperClass的initialize方法双妨。因為子類FinalClass的初始化觸發(fā)了超類SubClass淮阐、SuperClass的初始化。所以初始化FinalClass時刁品,實際上使這三個類都得到了初始化的機會泣特,自然就會連續(xù)調(diào)用三次SuperClass的initialize方法。
還是上面那三個類挑随,如果我們又給SubClass實現(xiàn)了initialize方法状您,那么控制臺將會輸出如下結(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

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

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

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

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

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

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

[NSImage self];

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

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

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

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

【必須】如果子類的designated初始化方法與超類的designated初始化方法不同荒给,則子類應(yīng)該覆寫超類的designated初始化方法丈挟。(因為開發(fā)者很有可能直接調(diào)用超類的某個designated方法來初始化一個子類對象,這樣也是合情合理的锐墙,但使用超類的方法初始化子類礁哄,可能會導(dǎo)致子類在初始化時缺失一些必要信息)长酗。

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

【必須】禁止子類的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咧叭,錯誤的
        if (self = [super initWithURL:url]) {

        }
        return self;
    }
    @end

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

【必須】另外禁止在init方法中使用self.xxx的方式訪問屬性蚀乔。如果存在繼承的情況下,很有可能導(dǎo)致崩潰菲茬。具體參考本人之前的文章《為什么不能在init和dealloc函數(shù)中使用accessor方法》

(2.3) Init error

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

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

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

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

- (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)該放在實現(xiàn)文件的最上面喉钢,并且剛好在 @synthesize 和 @dynamic 語句的后面。在任何類中良姆,init 都應(yīng)該直接放在 dealloc 方法的下面肠虽。

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

-(void)dealloc{
    [self unsafeMethod:self];
    // 因為當(dāng)前已經(jīng)在self這個指針?biāo)赶虻膶ο蟮匿N毀階段伯复,銷毀self所指向的對象已經(jīng)在所難免慨代。如果在unsafeMethod:中把self放到了autorelease poll中,那么self會被retain住啸如,計劃下個runloop周期在進行銷毀侍匙。但是dealloc運行結(jié)束后,self所指向的對象的內(nèi)存空間就直接被回收了叮雳,但是self這個指針還沒有銷毀(即沒有被置為nil)想暗,導(dǎo)致self變成了一個名副其實的野指針。
    // 到了下一個runloop周期帘不,因為self所指向的對象已經(jīng)被銷毀说莫,會因為非法訪問而造成crash問題。
}

【必須】和init方法一樣寞焙,禁止在dealloc方法中使用self.xxx的方式訪問屬性储狭。如果存在繼承的情況下,很有可能導(dǎo)致崩潰捣郊。具體參考本人之前的文章《為什么不能在init和dealloc函數(shù)中使用accessor方法》辽狈、、

(2.5) Block規(guī)范

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

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

(2.6) Notification規(guī)范

前面在命名規(guī)范一章中已經(jīng)介紹了通知的命名規(guī)范,這里解釋的是通知的使用規(guī)范娘扩。
通知作為觀察者模式的一個落地產(chǎn)物着茸,在開發(fā)中能夠?qū)崿F(xiàn)一對多的通信。所有可以使用delegate和block實現(xiàn)的通信和傳值琐旁,都可以使用通知實現(xiàn)涮阔。正因通知如此靈活,我們更應(yīng)該弄清楚通知適合使用的場景旋膳,避免把通知和delegate以及block等進行混淆澎语。
通知是一把雙刃劍,讓你歡喜讓你憂验懊。開發(fā)中擅羞,當(dāng)你走投無路將要崩潰時,可以考慮使用通知义图;而當(dāng)你頻繁使用通知時减俏,同樣會讓你崩潰到走投無路。所以碱工,在每個應(yīng)用中娃承,我們應(yīng)該時刻留意并控制通知的數(shù)量奏夫,避免通知滿天飛的現(xiàn)象。
曾經(jīng)有一個項目擺在我面前历筝,我卻無法珍惜酗昼,因為通知太多了,幾乎有代碼的地方就有通知梳猪。如果現(xiàn)在同樣有一個充滿通知的項目擺在我面前麻削,我知道是時候該優(yōu)化它了。

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

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

【必須】NSNotificationCenter在iOS8及更老的系統(tǒng)有一個多線程bug肺然,selector執(zhí)行到一半可能會因為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在哪個線程中post拾碌,就在哪個線程中被轉(zhuǎn)發(fā),而不一定是在注冊觀察者的那個線程中街望。如果post消息不在主線程校翔,而接受消息的回調(diào)里做了UI操作,需要讓其在主線程執(zhí)行灾前。
說明:每個進程都會創(chuàng)建一個NotificationCenter防症,這個center通過NSNotificationCenter defaultCenter獲取,當(dāng)然也可以自己創(chuàng)建一個center哎甲。
NoticiationCenter是以同步(非異步蔫敲,當(dāng)前線程,會等待炭玫,會阻塞)的方式發(fā)送請求奈嘿。即,當(dāng)post通知時吞加,center會一直等待所有的observer都收到并且處理了通知才會返回到poster裙犹。如果需要異步發(fā)送通知尽狠,請使用notificationQueue,在一個多線程的應(yīng)用中叶圃,通知會發(fā)送到所有的線程中袄膏。

(2.7) UI規(guī)范

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

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

【必須】UITableView使用self-sizing實現(xiàn)不等高cell時接癌,請在- (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)訪問一個 CGRectx缺猛, y缨叫, widthheight 時荔燎,應(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] 會block住當(dāng)前線程琐簇,知道所有的內(nèi)容都寫進磁盤,如果內(nèi)容過多座享,重復(fù)調(diào)用的話會嚴(yán)重影響性能婉商。

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

2.9 Collection規(guī)范

【必須】不要用一個可能為nil的對象初始化集合對象,否則可能會導(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]’

// 改進辦法:
NSObject *obj = somOjbcetMaybeNil;
NSMutableArray *arrM = nil;
if (obj && [obj isKindOfClass:[NSObject class]]) {
    arrM = [NSMutableArray arrayWithObject:obj];
} else {
    arrM = nil;
}

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

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

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

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

// 錯誤寫法
- (void)checkAllValidItems{
[self.allItems 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ù)體返回。以下使用NSArray舉例莹痢,其他集合類型同理种蘸。如下:

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

// 執(zhí)行結(jié)果:
// fall through

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

- (void)touchesBegan:(NSSet<UITouch *> *)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

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

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

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

【建議】集合類使用泛型來指定對象的類型亭饵。

@property(nonatomic,copy) NSArray<NSString *> *array;
@property(nonatomic,strong) NSMutableDictionary<NSString *,NSString *> *dictionary;

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

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

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

if (!error)  return success;

【必須】多于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];
}

【建議】對于條件語句的真假庇麦,因為 nil 解析為 NO计技,所以沒有必要在條件中與它進行比較。永遠不要直接和 YES 和 NO進行比較山橄,因為 YES 被定義為 1垮媒,而 BOOL 可以多達 8 位。

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

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

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

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

isEqual:方法允許我們傳入任意類型的對象作為參數(shù),如果參數(shù)類型和receiver(方法調(diào)用者)類型不一致朴艰,會返回NO观蓄。而isEqualToString:和isEqualToArray:這兩個方法會假設(shè)參數(shù)類型和receiver類型一致混移,也就是說,這兩個方法不會對參數(shù)進行類型檢查侮穿。因此這兩個方法性能更好但不安全歌径。如果我們是從外部數(shù)據(jù)源(比如info.plist或preferences)獲取的數(shù)據(jù),那么推薦使用isEqual:亲茅,因為這樣更安全回铛。如果我們知道參數(shù)的確切類型,那么可以使用類似于isEqualToString:這樣的方法克锣,因為性能更好茵肃。關(guān)于對象等同性判定的更多內(nèi)容,請參考筆者之前的文章《淺析對象等同性判斷》袭祟。

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

懶加載適合的場景:

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

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

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

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

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

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

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

 - (void)viewDidLoad {
   [super viewDidLoad];
   // 錯誤封孙。出現(xiàn)死鎖垢揩,報錯:EXC_BAD_INSTRUCTION。原因:在主隊列中同步的添加一個block到主隊列中
   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ù)加到了主隊列由主線程來執(zhí)行,但因為是異步呐籽,此時主隊列后面的任務(wù)不依賴于前面的任務(wù)锋勺。
   dispatch_queue_t mainQueue = dispatch_get_main_queue();
   dispatch_block_t block = ^() {
       NSLog(@"%@", [NSThread currentThread]);
   };
   dispatch_async(mainQueue, block);
}
// 打印結(jié)果:
// <NSThread: 0x600000073300>{number = 1, name = main}

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

【必須】在主線程中禁止進行同步網(wǎng)絡(luò)資源讀取狡蝶,使用NSURLSession進行異步獲取庶橱。當(dāng)然,你可以在子線程同步獲取網(wǎng)絡(luò)資源贪惹,但還是上面的那一條建議:避免使用dispatch_sync苏章,盡量使用dispatch_async。因為死鎖不一定只發(fā)生在主線程奏瞬。

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

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

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
   UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; 
   if (pasteboard.string.length > 0) {//這個方法會阻塞線程
      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時县耽,要注意是否有對象沒有被釋放掉(常見于CF對象),避免造成內(nèi)存泄露镣典。

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

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

【必須】在dealloc方法中蒸辆,禁止將self作為參數(shù)傳遞出去征炼,如果self被retain住,到下個runloop周期再釋放躬贡,則會造成多次釋放crash谆奥。這一點在dealloc一節(jié)中有說明。

【建議】除非你清除的知道自己在做什么拂玻。否則不建議將UIView類的對象加入到NSArray酸些、NSDictionary、NSSet中檐蚜。如有需要可以添加到NSMapTable 和 NSHashTable魄懂。因為NSArray、NSDictionary闯第、NSSet會對加入的對象做strong引用(即使你把加入的對象進行了weak)市栗。而NSMapTable、NSHashTable會對加入的對象做weak引用咳短。
說明:簡單的說填帽,NSHashTable相當(dāng)于weak的NSMutableArray;NSMapTable相當(dāng)于weak的NSMutableDictionary.

// 錯誤的例子:
@implementation WSObject
- (void)dealloc {
    NSLog(@"dealloc");
}
@end

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    WSObject *object = [WSObject new];
    // 即使對object進行了weak弱化咙好,數(shù)組也會強引用這個object對象盲赊。dealloc方法不會被執(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<UITouch *> *)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)釋放了哀蘑,但是控制臺仍然輸出 hashTable.count == 1。但是請相信我葵第,此時存在于hashTable中的那個object已經(jīng)變成了nil绘迁。不信你繼續(xù)看下面的例子:

- (void)touchesBegan:(NSSet<UITouch *> *)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的缀台,除非手動創(chuàng)建;而主線程是系統(tǒng)會自動創(chuàng)建Runloop的哮奇。所以在異步線程調(diào)用是請先確保該線程是有Runloop的膛腐。

使用performSelector:withObject:afterDelay:和cancelPreviousPerformRequestsWithTarget組合的時候要小心:
afterDelay會增加引用計數(shù),而cancel會對引用計數(shù)減一
如果receiver在引用計數(shù)器為1的時候鼎俘,調(diào)用cancel會立即回收receiver哲身。后續(xù)再次調(diào)用receiver的方法就會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)該說明其作用以及注意事項(如果有)实幕。
【建議】因為方法或?qū)傩员旧砭途哂凶晕颐枋鲂裕⑨寫?yīng)該簡明扼要赚导,說明是什么和為什么即可茬缩。

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

【建議】盡量減少繼承,類的繼承關(guān)系不要超過3層吼旧』宋可以考慮使用category、protocol來代替繼承圈暗。

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

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

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

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

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

參考 raywenderlich/objective-c-style-guide

#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 項目文件同步。Xcode 創(chuàng)建的任何組(group)都必須在文件系統(tǒng)有相應(yīng)的映射玛瘸。為了更清晰糊渊,代碼不僅應(yīng)該按照類型進行分組渺绒,也可以根據(jù)業(yè)務(wù)功能進行分組菱鸥。

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

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

本文參考文章

Coding Guidelines for Cocoa
Google Objective-C Style Guide
objective-c-style-guide
《Effective Objective-C 2.0 52 Specific Ways to Improve Your iOS and OS X Programs》

其他有價值的文章

App Programming Guide for iOS

文/VV木公子(簡書作者)
PS:如非特別說明趾诗,所有文章均為原創(chuàng)作品,著作權(quán)歸作者所有犀斋,轉(zhuǎn)載請聯(lián)系作者獲得授權(quán)叽粹,并注明出處虫几。

如果有技術(shù)問題辆脸,歡迎加入QQ群進行交流啡氢,群聊號碼:194236752空执。

我的博客即將搬運同步至騰訊云+社區(qū)穗椅,邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=39dpa7scoeio8

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末门坷,一起剝皮案震驚了整個濱河市默蚌,隨后出現(xiàn)的幾起案子绸吸,更是在濱河造成了極大的恐慌锦茁,老刑警劉巖叉存,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件稿存,死亡現(xiàn)場離奇詭異瓣履,居然都是意外死亡拂苹,警方通過查閱死者的電腦和手機瓢棒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門脯宿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來连霉,“玉大人跺撼,你說我怎么就攤上這事歉井×ㄖ粒” “怎么了菩貌?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵箭阶,是天一觀的道長仇参。 經(jīng)常有香客問我冈敛,道長抓谴,這世上最難降的妖魔是什么癌压? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任集侯,我火速辦了婚禮棠枉,結(jié)果婚禮上泡挺,老公的妹妹穿的比我還像新娘贱除。我一直安慰自己月幌,他們只是感情好扯躺,可當(dāng)我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布缅帘。 她就那樣靜靜地躺著钦无,像睡著了一般失暂。 火紅的嫁衣襯著肌膚如雪弟塞。 梳的紋絲不亂的頭發(fā)上决记,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天索昂,我揣著相機與錄音椒惨,去河邊找鬼康谆。 笑死沃暗,一個胖子當(dāng)著我的面吹牛描睦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播今艺,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼虚缎,長吁一口氣:“原來是場噩夢啊……” “哼实牡!你這毒婦竟也來了创坞?” 一聲冷哼從身側(cè)響起题涨,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎席函,沒想到半個月后茂附,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體何之,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡徊件,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年虱痕,在試婚紗的時候發(fā)現(xiàn)自己被綠了部翘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡夹囚,死狀恐怖荸哟,靈堂內(nèi)的尸體忽然破棺而出鞍历,到底是詐尸還是另有隱情劣砍,我是刑警寧澤秆剪,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站洁灵,受9級特大地震影響徽千,放射性物質(zhì)發(fā)生泄漏双抽。R本人自食惡果不足惜牍汹,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一嫁蛇、第九天 我趴在偏房一處隱蔽的房頂上張望睬棚。 院中可真熱鬧抑党,春花似錦新荤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽笆呆。三九已至赠幕,卻和暖如春榕堰,著一層夾襖步出監(jiān)牢的瞬間圾旨,已是汗流浹背砍的。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工廓鞠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留诫惭,地道東北人夕土。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像篮撑,于是被迫代替她去往敵國和親赢笨。 傳聞我的和親對象是個殘疾皇子茧妒,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內(nèi)容

  • 前言 說是前言桐筏,其實也是本文誕生的目的。隨著公司業(yè)務(wù)的不斷增加拇砰,功能的快速迭代梅忌,app的業(yè)務(wù)線越來越多,代碼體積變...
    夢翔_d674閱讀 1,491評論 0 2
  • 前言說是前言除破,其實也是本文誕生的目的牧氮。隨著公司業(yè)務(wù)的不斷增加皂岔,功能的快速迭代蹋笼,app的業(yè)務(wù)線越來越多,代碼體積變得...
    Mr_yinwei閱讀 641評論 0 0
  • 前言 說是前言,其實也是本文誕生的目的剖毯。隨著公司業(yè)務(wù)的不斷增加圾笨,功能的快速迭代,app的業(yè)務(wù)線越來越多逊谋,代碼體積變...
    Yealink閱讀 5,292評論 0 13
  • 約定 在我看來擂达,開發(fā)規(guī)范像是一條可供參考的標(biāo)準(zhǔn)線。不同開發(fā)者可以根據(jù)這條標(biāo)準(zhǔn)線來規(guī)范自己的開發(fā)行為胶滋,尤其是在大的項...
    xxzsxxzs閱讀 619評論 1 0
  • 本規(guī)范原文來自Github.但是markdown格式有問題板鬓,我重新排了下。實際開發(fā)中根據(jù)具體情況決定如何取舍究恤,但是...
    LeonXtp閱讀 465評論 0 0