原文地址:mp.weixin.qq.com
前言
說是前言幔虏,其實也是本文誕生的目的。隨著公司業(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)運(yùn)而生精置。
因筆者現(xiàn)在所就職公司的開發(fā)規(guī)范主導(dǎo)編寫,目前公司業(yè)務(wù)的迭代都在按照這個規(guī)范在有條不紊的進(jìn)行锣杂。綜合之前編寫規(guī)范的經(jīng)驗脂倦,歷時一個月的時間,斷斷續(xù)續(xù)重新梳理了一份比較全面元莫、比較完整的iOS開發(fā)者規(guī)范赖阻,希望這些條條框框能夠給正在閱讀的你提供一些參考的價值。也希望越來越多的iOS開發(fā)者能夠養(yǎng)成優(yōu)秀的編碼習(xí)慣踱蠢。如果你覺得個別地方不妥或者有需要補(bǔ)充的規(guī)范火欧,請留言或者私信,我會第一時間響應(yīng)茎截。
約定
在我看來苇侵,開發(fā)規(guī)范像是一條可供參考的標(biāo)準(zhǔn)線。不同開發(fā)者可以根據(jù)這條標(biāo)準(zhǔn)線來規(guī)范自己的開發(fā)行為企锌,尤其是在大的項目中榆浓,開發(fā)規(guī)范可以約束不同開發(fā)者的開發(fā)風(fēng)格,使項目從細(xì)節(jié)到整體上都能達(dá)到風(fēng)格統(tǒng)一撕攒,利于維護(hù)陡鹃。
本文的開發(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)重后果。
【建議】:建議遵守从橘。長期遵守這樣的約定念赶,有助于維護(hù)系統(tǒng)的穩(wěn)定和提高合作效率。
本文參考了蘋果官方編碼指南和github上一些知名的編碼規(guī)范恰力,也算是取眾人之所長叉谜,集百家之精華的一篇文章。讀者可以根據(jù)自己的實際需要和興趣點(diǎn)來選擇性的閱讀踩萎。本文主題部分主要由以下兩章(共32節(jié))構(gòu)成:
(一) 命名規(guī)范
通用命名規(guī)范(講述命名的一些通用規(guī)范)
縮寫規(guī)范(講述常見的縮寫以及縮寫規(guī)范)
Method命名規(guī)范(講述方法命名的具體規(guī)范)
Accessor命名規(guī)范(講述set和get方法的命名規(guī)范)
Parameter命名規(guī)范(講述參數(shù)命名規(guī)范)
Delegate方法命名規(guī)范(講述delegate方法的命名規(guī)范)
Private方法命名規(guī)范(講述私有方法的命名規(guī)范)
Category命名規(guī)范(講述分類的命名規(guī)范)
Class命名規(guī)范(講述類命名規(guī)范)
Protocol命名規(guī)范(講述協(xié)議的命名規(guī)范)
Notification命名規(guī)范(講述通知的命名規(guī)范)
Constant命名規(guī)范(講述枚舉常量以及const常量的命名規(guī)范)
Exception命名規(guī)范(講述異常的命名規(guī)范)
(二)編碼規(guī)范
Initialize方法(講述類的initialize方法的使用規(guī)范)
Init方法(講述初始化方法的設(shè)計規(guī)范包括designated init方法和secondary init方法)
Init error(講述init方法初始化對象失敗時的錯誤處理)
Dealloc規(guī)范(講述dealloc方法的使用規(guī)范)
Block規(guī)范(講述block的使用規(guī)范)
Notification規(guī)范(講述通知的使用規(guī)范)
UI規(guī)范(講述開發(fā)UI時的一些規(guī)范)
IO規(guī)范(講述讀寫文件時的一些注意事項)
Collection規(guī)范(講述集合類型的使用規(guī)范)
分支語句規(guī)范(講述常用的分支語句if正罢、switch語句的編碼規(guī)范)
對象判等規(guī)范(講述常用的判定對象等同性的方法使用規(guī)范)
懶加載規(guī)范(講述懶加載的使用規(guī)范)
多線程規(guī)范(講述多線程環(huán)境下的一些編碼規(guī)范)
內(nèi)存管理規(guī)范(講述編碼過程中常見的內(nèi)存管理注意點(diǎn))
延遲調(diào)用規(guī)范(講述使用延遲方法時注意事項)
注釋規(guī)范(講述編碼中注釋的使用規(guī)范)
類的設(shè)計規(guī)范(講述類的設(shè)計規(guī)范)
代碼組織規(guī)范(講述類中的代碼組織規(guī)范)
工程結(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ù)/變量/常量/宏 的命名必須具有自我描述性鳞尔。杜絕中文拼音、過度縮寫早直、或者無意義的命名方式寥假。
【必須】禁止自我指涉。屬性/局部變量/成員變量不要自我指涉霞扬。通知和掩碼常量(通常指那些可以進(jìn)行按位運(yùn)算的枚舉值) 除外糕韧。
通俗的講,自我指涉是指在變量末尾增加了自己類型的一個后綴喻圃。
命名 | 說明 |
---|---|
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: | 不清晰三椿,移除什么缺菌? |
【建議】一般情況下,不要縮寫或省略單詞搜锰,建議拼寫出來伴郁,即使它有點(diǎn)長。當(dāng)然蛋叼,在保證可讀性的同時焊傅,for循環(huán)中遍歷出來的對象或者某些方法的參數(shù)可以縮寫。
命名 | 說明 |
---|---|
destinationSelection | 規(guī)范寫法 |
destSel | 不清晰 |
setBackgroundColor: | 規(guī)范寫法 |
setBkgdColor: | 不清晰 |
(1.2) 縮寫規(guī)范
通常狈涮,我們都不應(yīng)該縮寫命名(參考General Principles)狐胎。然而,下面所列舉的都是一些眾所周知的縮寫歌馍,我們可以繼續(xù)使用這些古老的縮寫握巢。在其他情況下,我們需要遵循下面兩條縮寫建議:
允許使用那些在C語言時代就已經(jīng)在使用的縮寫松却,比如alloc和getc暴浦。
我們可以在命名參數(shù)的時候使用縮寫。其他情況晓锻,盡量不要使用縮寫歌焦。
我們也可以使用計算機(jī)行業(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)團(tuán)隊經(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)該單獨(dú)的提供一個方法匿值,并在新的方法后面添加上必要的關(guān)鍵參數(shù)赠制。
命名 | 說明 |
---|---|
- (id)viewWithTag:(NSInteger)aTag; | OK |
- (id)taggedView:(int)aTag; | 不OK |
// 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 |
【建議】如果方法描述了兩個獨(dú)立的動作绊谭,可以使用“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;
【建議】如果屬性表示一個形容詞迫筑,推薦格式如下:
- (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)的私有方法和父類中的某個私有方法同名加酵。在運(yù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)該盡可能的獨(dú)特门扇。也許這個前綴是基于你公司或者項目的縮寫,比如”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字符串進(jìn)行標(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ī)范
(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)建浮點(diǎn)型常量犬性。你也可以使用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)建格侯;對于浮點(diǎn)型常量,使用const修飾符創(chuàng)建财著。
【必須】有些符號需要使用大寫字母標(biāo)識。預(yù)處理器需要根據(jù)這個符號進(jìn)行計算以便決定是否要對某一塊代碼進(jìn)行處理撑碴。比如:
#ifdef DEBUG
注意:那些編譯器定義的宏撑教,左側(cè)和右側(cè)各有兩個下劃線。如下:
__MACH__
【必須】通知的名字和字典的key醉拓,應(yīng)該使用字符串常量來定義伟姐。使用字符串常量編譯器可以進(jìn)行檢查,這樣可以避免拼寫錯誤亿卤。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ī)范
上面已經(jīng)有一節(jié)介紹過通知的命名規(guī)范秆乳。異常和通知的命名遵循相似的規(guī)則,但又各有不同钻哩。
【必須】和Notification的命名規(guī)范一樣(可參考Notification命名規(guī)范一節(jié))屹堰,異常也是用全局的NSString字符串進(jìn)行標(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方法叶堆,運(yùn)行時初次調(diào)用這個類的時候,系統(tǒng)會沿著繼承鏈(類繼承體系)斥杜,先后給繼承鏈上游中的每個超類發(fā)送一條initialize消息虱颗,直到某個超類實現(xiàn)了initlialize方法沥匈,才會停止向上調(diào)用。因此忘渔,在運(yùn)行時高帖,某個類的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時递胧,實際上使這三個類都得到了初始化的機(jī)會,自然就會連續(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ù)存儲機(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周期在進(jìn)行銷毀。但是dealloc運(yùn)行結(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等進(jìn)行混淆。
通知是一把雙刃劍述呐,讓你歡喜讓你憂惩淳。開發(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í)行遂鹊。
說明:每個進(jìn)程都會創(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)訪問一個 CGRect 的 x, y泌豆, width定庵, height 時,應(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)容都寫進(jìn)磁盤畴博,如果內(nèi)容過多,重復(fù)調(diào)用的話會嚴(yán)重影響性能蓝仲。
【建議】一些經(jīng)常被使用的文件建議做好緩存俱病。避免重復(fù)的IO操作蜜唾。建議只有在合適的時候再進(jìn)行持久化操作。
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]’
// 改進(jìn)辦法:
NSObject *obj = somOjbcetMaybeNil;
NSMutableArray *arrM = nil;
if (obj && [obj isKindOfClass:[NSObject class]]) {
arrM = [NSMutableArray arrayWithObject:obj];
} else {
arrM = nil;
}
【必須】同理,對插入到集合對象里面的對象也要進(jìn)行判空咱揍。
【必須】注意在多線程環(huán)境下訪問可變集合對象的問題颖榜,必要時應(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
}]; }
// 錯誤寫法
- (void)checkAllValidItems{
[self.allItems enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
//do something using obj
// 如果在enumerate過程中,其他線程對allItems這個可變集合進(jìn)行了變更操作题翰,這里就有可能引發(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個邏輯表達(dá)式必須用參數(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尝抖,所以沒有必要在條件中與它進(jìn)行比較。永遠(yuǎn)不要直接和 YES 和 NO進(jìn)行比較迅皇,因為 YES 被定義為 1昧辽,而 BOOL 可以多達(dá) 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ù)進(jìn)行類型檢查腔丧。因此這兩個方法性能更好但不安全。如果我們是從外部數(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}
【必須】禁止在非主線程中進(jìn)行UI元素的操作尝哆。
【必須】在主線程中禁止進(jìn)行同步網(wǎng)絡(luò)資源讀取秉撇,使用NSURLSession進(jìn)行異步獲取。當(dāng)然秋泄,你可以在子線程同步獲取網(wǎng)絡(luò)資源琐馆,但還是上面的那一條建議:避免使用dispatch_sync,盡量使用dispatch_async恒序。因為死鎖不一定只發(fā)生在主線程瘦麸。
【必須】如果需要進(jìn)行大文件或者多文件的IO操作,禁止主線程使用歧胁,必須進(jìn)行異步處理滋饲。
【必須】對剪貼板的讀取必須要放在異步線程處理,最新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)該知道單例的特點(diǎn)和優(yōu)勢逢并,也必須要弄明白單例適合的場景。UIApplication郭卫、access database 砍聊、request network 、access userInfo這類全局僅存在一份的對象或者需要多線程訪問的對象贰军,可以使用單例玻蝌。不要僅僅為了訪問方便就使用單例。
【建議】單例初始化方法中盡量保證單一職責(zé),尤其不要進(jìn)行其他單例的調(diào)用词疼。極端情況下俯树,兩個單例對象在各自的單例初始化方法中調(diào)用,會造成死鎖贰盗。
【必須】在dealloc方法中许饿,禁止將self作為參數(shù)傳遞出去,如果self被retain住舵盈,到下個runloop周期再釋放陋率,則會造成多次釋放crash。這一點(diǎn)在dealloc一節(jié)中有說明秽晚。
【建議】除非你清除的知道自己在做什么瓦糟。否則不建議將UIView類的對象加入到NSArray、NSDictionary赴蝇、NSSet中菩浙。如有需要可以添加到NSMapTable 和 NSHashTable。因為NSArray句伶、NSDictionary芍耘、NSSet會對加入的對象做strong引用(即使你把加入的對象進(jìn)行了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進(jìn)行了weak弱化坝初,數(shù)組也會強(qiáng)引用這個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ìn)行分組,也可以根據(jù)業(yè)務(wù)功能進(jìn)行分組涯曲。
【建議】合理組織工程的內(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》
其他有價值的文章
更多推薦
作者:VV木公子
鏈接:http://www.reibang.com/p/c818c00e0690
著作權(quán)歸作者所有徽曲。轉(zhuǎn)載請聯(lián)系作者獲得授權(quán)