前言
說是前言,其實也是本文誕生的目的揉稚。隨著公司業(yè)務的不斷增加秒啦,功能的快速迭代,app的業(yè)務線越來越多搀玖,代碼體積變得越來越龐大余境。同時,app投入的開發(fā)者也也越來越多灌诅,不同的開發(fā)者的code風格千差萬別芳来。加之公司開發(fā)者人員變動,為了保證app穩(wěn)定性猜拾,保證開發(fā)效率壕鹉,統(tǒng)一開發(fā)風格烈炭。于是灌侣,這篇iOS開發(fā)規(guī)范應運而生褥傍。
因筆者現在所就職公司的開發(fā)規(guī)范主導編寫,目前公司業(yè)務的迭代都在按照這個規(guī)范在有條不紊的進行宋雏。綜合之前編寫規(guī)范的經驗,歷時一個月的時間务豺,斷斷續(xù)續(xù)重新梳理了一份比較全面磨总、比較完整的iOS開發(fā)者規(guī)范,希望這些條條框框能夠給正在閱讀的你提供一些參考的價值笼沥。也希望越來越多的iOS開發(fā)者能夠養(yǎng)成優(yōu)秀的編碼習慣蚪燕。如果你覺得個別地方不妥或者有需要補充的規(guī)范,請留言或者私信奔浅,我會第一時間響應馆纳。
約定
在我看來,開發(fā)規(guī)范像是一條可供參考的標準線汹桦。不同開發(fā)者可以根據這條標準線來規(guī)范自己的開發(fā)行為鲁驶,尤其是在大的項目中,開發(fā)規(guī)范可以約束不同開發(fā)者的開發(fā)風格舞骆,使項目從細節(jié)到整體上都能達到風格統(tǒng)一钥弯,利于維護。
本文的開發(fā)規(guī)范由很多item組成督禽,不同的item描述了不同的問題脆霎。每一個item就是一條具體的開發(fā)規(guī)范,違反不同的開發(fā)規(guī)范狈惫,也會引起不同嚴重程度的后果睛蛛。就像法律和道德的差異一樣,我們必須遵守法律,不然可能帶來損人不利己的嚴重后果忆肾,但有些人雖然沒有觸犯法律菠红,卻違背了道德,雖然暫時沒有產生嚴重的后果难菌,長此以往试溯,也會形成一種壞的風氣。所以郊酒,無論法律和道德遇绞,我們都該鞭策自己成為優(yōu)秀的人,而不該止步于一個合格的人燎窘。同理摹闽,開發(fā)規(guī)范也是如此,我們必須遵守那些必須要遵守的開發(fā)規(guī)范褐健,提倡遵守那些建議你遵守的開發(fā)規(guī)范付鹿。所以,根據約束力度蚜迅,我們把開發(fā)規(guī)范暫時劃分成兩個等級舵匾,分別是【必須】、【建議】谁不。
- 【必須】:必須遵守坐梯。是不得不遵守的約定,一旦違反極有可能引起嚴重后果刹帕。
- 【建議】:建議遵守吵血。長期遵守這樣的約定,有助于維護系統(tǒng)的穩(wěn)定和提高合作效率偷溺。
本文參考了蘋果官方編碼指南和github上一些知名的編碼規(guī)范蹋辅,也算是取眾人之所長,集百家之精華的一篇文章挫掏。讀者可以根據自己的實際需要和興趣點來選擇性的閱讀侦另。本文主題部分主要由以下兩章(共32節(jié))構成:
(一) 命名規(guī)范
- 通用命名規(guī)范(講述命名的一些通用規(guī)范)
- 縮寫規(guī)范(講述常見的縮寫以及縮寫規(guī)范)
- Method命名規(guī)范(講述方法命名的具體規(guī)范)
- Accessor命名規(guī)范(講述set和get方法的命名規(guī)范)
- Parameter命名規(guī)范(講述參數命名規(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方法(講述初始化方法的設計規(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ī)范)
- 內存管理規(guī)范(講述編碼過程中常見的內存管理注意點)
- 延遲調用規(guī)范(講述使用延遲方法時注意事項)
- 注釋規(guī)范(講述編碼中注釋的使用規(guī)范)
- 類的設計規(guī)范(講述類的設計規(guī)范)
- 代碼組織規(guī)范(講述類中的代碼組織規(guī)范)
- 工程結構規(guī)范(講述工程的文件組織規(guī)范)
(一)命名規(guī)范
根據Cocoa編碼規(guī)范里的描述砍濒,以前情況下淋肾,命名應該遵循以下基本原則:Clarity、Consistency爸邢、No Self Reference樊卓。即清晰性、一致性杠河、不要自我指涉Code Naming Basics碌尔。
(1.1) 通用命名規(guī)則
一般情況下浇辜,通用命名規(guī)則適用于變量、常量唾戚、屬性柳洋、參數、方法叹坦、函數等熊镣。當然也有例外,下面我們會針對于每一種情況一一列舉募书。
【必須】自我描述性绪囱。屬性/函數/參數/變量/常量/宏 的命名必須具有自我描述性。杜絕中文拼音莹捡、過度縮寫鬼吵、或者無意義的命名方式。
【必須】禁止自我指涉篮赢。屬性/局部變量/成員變量不要自我指涉齿椅。通知和掩碼常量(通常指那些可以進行按位運算的枚舉值) 除外。
通俗的講启泣,自我指涉是指在變量末尾增加了自己類型的一個后綴涣脚。
命名 | 說明 |
---|---|
NSString | 規(guī)范的寫法 |
NSStringObject | 自我指涉(不規(guī)范) |
掩碼常量、通知除外:
命名 | 說明 |
---|---|
NSUnderlineByWordMask | 規(guī)范的寫法 |
NSTableViewColumnDidMoveNotification | 自我指涉(不規(guī)范) |
【必須】駝峰命名方式种远。參數名涩澡、成員變量、局部變量坠敷、屬性名都要采用小寫字母開頭的駝峰命名方式。如果方法名以一個眾所周知的大寫縮略詞開始射富,可以不適用駝峰命名方式膝迎。比如FTP、WWW等胰耗。
【建議】一致性限次。屬性/函數/參數/變量/常量/宏 的命名應該具有上下文或者全局的一致性,相同類型或者具有相同作用的變量的命名方式應該相同或者類似柴灯。
說明:具體來講卖漫,不同文件中或者不同類中具有相同功能或相似功能的屬性的命名應該是相同的或者相似的。好處在于:方便后來的開發(fā)者減少代碼的閱讀量和提高對代碼的理解速度赠群。比如:
// count同時定義在NSDictionary羊始、NSArray、NSSet這三個集合類中查描。且這三個集合類中的count屬性都代表同一個意思突委,即集合中對象的個數柏卤。
@property (readonly) NSUInteger count;
【必須】清晰性。屬性/函數/參數/變量/常量/宏 的命名應該保持清晰+簡潔匀油,如果魚和熊掌不能兼得缘缚,那么清晰更重要。
命名 | 說明 |
---|---|
insertObject:atIndex: | 規(guī)范的寫法 |
insert:at: | 不清晰敌蚜,插入什么桥滨?at代表什么? |
removeObjectAtIndex: | 規(guī)范的寫法 |
removeObject: | 規(guī)范的寫法弛车,因為參數指明了要移除一個對象 |
remove: | 不清晰齐媒,移除什么? |
建議】一般情況下帅韧,不要縮寫或省略單詞里初,建議拼寫出來,即使它有點長忽舟。當然双妨,在保證可讀性的同時,for循環(huán)中遍歷出來的對象或者某些方法的參數可以縮寫叮阅。
命名 | 說明 |
---|---|
destinationSelection | 規(guī)范的寫法 |
destSel | 不清晰 |
setBackgroundColor: | 規(guī)范的寫法 |
setBkgdColor: | 不清晰 |
(1.2) 縮寫規(guī)范
通常刁品,我們都不應該縮寫命名(參考General Principles)。然而浩姥,下面所列舉的都是一些眾所周知的縮寫挑随,我們可以繼續(xù)使用這些古老的縮寫。在其他情況下勒叠,我們需要遵循下面兩條縮寫建議:
- 允許使用那些在C語言時代就已經在使用的縮寫兜挨,比如alloc和getc。
- 我們可以在命名參數的時候使用縮寫眯分。其他情況拌汇,盡量不要使用縮寫。
我們也可以使用計算機行業(yè)通用的縮寫弊决。包括但不限于HTML噪舀、URL、RTF飘诗、HTTP与倡、TIFF、JPG昆稿、PNG纺座、GIF、LZW貌嫡、ROM比驻、RGB该溯、CMYK、MIDI别惦、FTP狈茉。
(1.3) Method命名規(guī)范
必須】方法名也要采用小寫字母開頭的駝峰命名方式。如果方法名以一個中所周知的大寫縮略詞開頭(比如HTTP)掸掸,該規(guī)則可以忽略氯庆。
【建議】一般情況下,不要在方法名稱中使用前綴扰付,因為他存在于特定類的命名空間中堤撵。
【建議】類、協(xié)議羽莺、函數实昨、常量、枚舉等全局可見內容需要添加三個字符作為前綴盐固。蘋果保留對任意兩個字符作為前綴的使用權荒给。所以盡量不要使用兩個字符作為前綴。禁止使用的前綴包括但不限于:NS,UI,CG,CF,CA,WK,MK,CI,NC刁卜。
【必須】禁止在方法前面加下劃線“ _ ”志电。Apple官網團隊經常在方法前面加下劃線"_"。為了避免方法覆蓋蛔趴,導致不可預知的意外挑辆,禁止在方法前面加下劃線。
【必須】自我描述性孝情。方法的命名也應該具有自我描述性鱼蝉。杜絕中文拼音、過度縮寫箫荡、或者無意義的命名方式蚀乔。
【建議】一致性。方法的命名也應該具有上下文或者全局的一致性菲茬,相同類型或者具有相同作用的方法的命名方式應該相同或者類似。
// 該方法同時定義在NSView派撕、NSControl婉弹、NSCell這三個類里面。
- (NSInteger)tag;
// 該屬性同時定義在NSDcitionary和NSArray中终吼。
@property (readonly) NSUInteger count;
【必須】蘋果爸爸說:如果一個方法代表某個名詞執(zhí)行的動作镀赌,則該方法應該以一個動詞開頭。如下:
- (void)invokeWithTarget:(id)target;
- (void)selectTabViewItem:(NSTabViewItem *)tabViewItem
【必須】蘋果爸爸還說:如果方法代表對象接收的動作际跪,那么方法一動詞開頭商佛。但不要使用“do”或者"does"作為方法名稱的一部分喉钢,因為這些助動詞不能為方法名稱增加太多的意義,反而讓方法看起來更加臃腫良姆。同時肠虽,也請不要在動詞前面使用副詞或者形容詞。
【必須】如果方法返回接收者的某個屬性玛追,那么請直接以屬性名作為方法名税课。如果方法間接的返回一個或多個值,我們可以使用“getxxx”的方式來命名方法痊剖。相反韩玩,無需額外的在方法名前面添加"get"。
命名 | 說明 |
---|---|
- (NSSize)cellSize; | OK |
- (NSSize)calcCellSize; | 不OK |
- (NSSize)getCellSize; | 不OK |
【必須】只有當方法間接的返回對象或數值陆馁,才有必要在方法名稱中使用“get”找颓,這種格式只適用于返回多個數據項的情況。如下:
// 通過傳入指針叮贩,來獲得多個值
- (void)getLineDash:(float *)pattern count:(int*)count phase:(float *)phase;
// NSURLCache (NSURLSessionTaskAdditions)中聲明的方法
- (void)getCachedResponseForDataTask:(NSURLSessionDataTask *)dataTask completionHandler:(void (^) (NSCachedURLResponse * __nullable cachedResponse))completionHandler;
【必須】所有參數前面都應該添加關鍵字击狮,除非你能保證每個人都能意會到你的精神。
命名 | 說明 |
---|---|
- (void)sendAction:(SEL)aSelector toObject:(id)anObject forAllCells:(BOOL)flag; | OK |
- (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag; | 不OK |
【建議】蘋果爸爸說:參數之前的單詞盡量能描述參數的意義妇汗。
命名 | 說明 |
---|---|
- (id)viewWithTag:(NSInteger)aTag; | OK |
- (id)taggedView:(int)aTag; | 不OK |
【必須】如果當前子類創(chuàng)建的方法比從父類繼承來的方法更加具體明確帘不。本身提供的方法更具有針對性。則不該重寫類本身提供的方法杨箭。而是應該單獨的提供一個方法寞焙,并在新的方法后面添加上必要的關鍵參數。
命名 | 說明 |
---|---|
- (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)建的方法參數的增加,這將會帶來一系列的問題慈参。
命名 | 說明 |
---|---|
- (int)runModalForDirectory:(NSString *)path file:(NSString *) name types:(NSArray *)fileTypes; | OK |
- (int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes; | 不OK |
【建議】如果方法描述了兩個獨立的動作呛牲,可以使用“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;
【必須】不要把動詞的過去分詞形式當做形容詞來使用
命名 | 說明 |
---|---|
- (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 |
【建議】只有方法間接的返回一個數值,或者需要多個數值需要被返回的時候掰邢,才有必要在方法名稱中使用“get”牺陶。
像這種接收多個參數的方法應該能夠傳入nil伟阔,因為調用者未必對每個參數都感興趣
- (void)getLineDash:(float *)pattern count:(int *)count phase:(float *)phase;
(1.5) Parameter命名規(guī)范
【必須】不要使用 ”pointer” 或 ”ptr” 命名參數,應該使用參數類型而非它的名字來代表他是否是一個指針掰伸。Method Arguments
(1.6) Delegate方法命名規(guī)范
delegate methods 又叫做delegation methods皱炉,如果delegate對象實現了另一個對象的delegate方法,那么這個對象就可以在它自己某個指定的事件發(fā)生時調用delegate對象的delegate方法碱工。delegate方法的命名有一些與眾不同的格式:
【建議】以觸發(fā)消息的對象名開頭娃承,省略類名前綴并且首字母小寫:
- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row;
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename;
【建議】除非delegate方法只有一個參數,即觸發(fā)delegate方法調用的delegating對象怕篷,否則冒號是緊跟在類名后面的历筝。
- (BOOL)applicationOpenUntitledFile:(NSApplication *)sender;
【建議】發(fā)送通知后再觸發(fā)delegate方法是一個例外:當delegate方法的調用是為了告訴delegate對象,某個通知已經被發(fā)送時廊谓,這個delegate方法的參數應該是通知對象梳猪,而非觸發(fā)delegate方法的對象。
- (void)windowDidChangeScreen:(NSNotification *)notification;
【建議】使用did或will這兩個情態(tài)動詞通知delegate對象某件事已經發(fā)生或將要發(fā)生蒸痹。
- (void)browserDidScroll:(NSBrowser *)sender;
- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window;
【建議】雖然我們可以在delegate方法中使用did和will來詢問delegate是否可以代替另一個對象做某件事情春弥,但是使用should看起來更加完美。
- (BOOL)windowShouldClose:(id)sender;
(1.7) Private方法命名規(guī)范
大部分情況下叠荠,私有方法的命名和公有方法的命名規(guī)則是一樣的匿沛。然而,通常情況下應該給私有方法添加一個前綴榛鼎,目的是和公有方法區(qū)分開逃呼。盡管這樣,這種給私有方法加前綴的命名方式有可能引起一些奇怪的問題者娱。問題就是:當你從Cocoa framework(即Cocoa系統(tǒng)庫)中的某個類派生出來一個子類時抡笼,你并不知道你的子類中定義的私有方法是否覆蓋了父類的私有方法,即有可能你自己在子類中實現的私有方法和父類中的某個私有方法同名黄鳍。在運行時推姻,這極有可能導致一些莫名其妙的問題,并且調試追蹤問題的難度也是相當大框沟。
Cocoa frameworks(Cocoa系統(tǒng)庫)中的私有方法通常以一個下劃線“ _ ”開頭藏古,用于標記這些方法是私有的(比如, _fooData ) 忍燥。不要問我為什么他們這么做校翔,這大概就是Apple工程師的開發(fā)習慣≡智埃基于這個事實,提供以下兩條建議:
【必須】禁止使用下劃線“ _ “作為私有方法的開頭孟辑。Apple已經預留這種私有方法的命名習慣哎甲。
【建議】如果你是要子類化Cocoa Frameworks中的一個非常龐大復雜的類(比如NSView或UIView)蔫敲,并且你想絕對的確保你自己的子類中的私有方法名和父類中的私有方法名不重復。你可以添加一個你自己的前綴作為私有方法的前綴炭玫,這個前綴應該盡可能的獨特奈嘿。也許這個前綴是基于你公司或者項目的縮寫,比如”XX_“吞加。
盡管給私有方法增加前綴看起來和”方法存在于他們的類的命名空間中“這一之前的說法有些沖突裙犹,但此處的意圖是:為子類私有方法添加前綴僅僅是為了保證子類方法和父類方法名稱不沖突。
【必須】不要在參數的名稱中使用“pointer”或者"ptr"衔憨。應該使用參數的類型來說明參數是否是一個指針叶圃。
【必須】不要使用一到兩個字符作為參數名。
【必須】不要對參數的每個單詞都縮寫践图。
【建議】如果調用某個方法是為了通知delegate某個事件"即將"發(fā)生或者"已經"發(fā)生掺冠,則請在方法名稱中使用“will”或者“did”這樣的助動詞。例如:
- (void)applicationWillResignActive:(UIApplication *)application;
- (void)applicationDidEnterBackground:(UIApplication *)application;
【建議】如果調用某個方法是為了要求delegate代表其他對象執(zhí)行某件事情码党,我們應該在方法中使用“should”這樣的情態(tài)動詞德崭。當然,也可以在方法中使用“did”或者“will”這樣的字眼揖盘,但更傾向于前者眉厨。
- (BOOL)tableViewSholdScroll:(id)sender;
(1.8) Category命名規(guī)范
【必須】category中不要聲明屬性和成員變量。
【必須】避免category中的方法覆蓋系統(tǒng)方法兽狭『豆桑可以使用前綴來區(qū)分系統(tǒng)方法和category方法淋样。但前綴不要僅僅使用下劃線”_“丘侠。
【建議】如果一個類比較復雜铸董,建議使用category的方式組織代碼莽龟。具體可以參考UIView畔裕。
(1.9) Class命名規(guī)范
必須】class的名稱應該由兩部分組成央碟,前綴+名稱序无。即械念,class的名稱應該包含一個前綴和一個名詞蒸健。
(1.10) Protocol命名規(guī)范
命名 | 說明 |
---|---|
NSLocking | OK |
NSLock | 不好座享,看起來像是一個類名 |
【建議】有時候protocol只是聲明了一堆相關方法,并不關聯(lián)class似忧。這種不關聯(lián)class的protocol使用ing形式以和class區(qū)分開來渣叛。比如NSLocking而非NSLock。
命名 | 說明 |
---|---|
NSLocking | OK |
NSLock | 不好盯捌,看起來像是一個類名 |
命名 | 說明 |
---|---|
UITableViewDelegate | OK |
NSObjectProtocol | OK |
【建議】如果proctocol不僅聲明了一堆相關方法淳衙,還關聯(lián)了某個class。這種關聯(lián)class的protocol的命名取決于關聯(lián)的class,然后再后面再加上protocol或delegate用于顯示的聲明這是一份協(xié)議箫攀。
命名 | 說明 |
---|---|
UITableViewDelegate | OK |
NSObjectProtocol | OK |
(1.11) Notification命名規(guī)范
【建議】蘋果爸爸說:如果一個類聲明了delegate屬性肠牲,通常情況下,這個類的delegate對象可以通過實現的delegate方法收到大部分通知消息靴跛。那么缀雳,這些通知的名稱應該反映出對應的delegate方法。比如梢睛,application對象發(fā)送的NSApplicationDidBecomeActiveNotification通知和對應的applicationDidBecomeActive:消息肥印。其實,這也算是命名的一致性要求绝葡。
【必須】notification的命名使用全局的NSString字符串進行標識深碱。命名方式如下:
[Name of associated class] + [Did | Will] + [UniquePartOfName] + Notification
例如:
NSApplicationDidBecomeActiveNotification
NSWindowDidMiniaturizeNotification
NSTextViewDidChangeSelectionNotification
NSColorPanelColorDidChangeNotification
【必須】object
通常是指發(fā)出notification的對象,如果在發(fā)送notification
的同時要傳遞一些額外的信息挤牛,請使用userInfo
莹痢,而不是object
。
【必須】如果某個通知是為了告知外界某個事件"即將"發(fā)生或者"已經"發(fā)生墓赴,則請在通知名稱中使用will
或者did
這樣的助動詞竞膳。例如:
UIKeyboardWillChangeFrameNotification;
UIKeyboardDidChangeFrameNotification;
(1.12) Constant命名規(guī)范
(1.12.1) 枚舉常量
【必須】使用枚舉類型來表示一組相關的整型常量。
【建議】枚舉常量和typedef定義的枚舉類型的命名規(guī)范同函數的命名規(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關鍵字創(chuàng)建常量
【必須】使用const關鍵字創(chuàng)建浮點型常量章办。你也可以使用const來創(chuàng)建和其他常量不相關的整型常量锉走。否則,請使用枚舉類型來創(chuàng)建藕届。即挪蹭,如果一個整型常量和其他常量不相關,可以使用const來創(chuàng)建休偶,否則梁厉,使用枚舉類型表示一組相關的整型常量。
以下例子聲明了const常量的格式:
const float NSLightGray;
(1.12.3) 其他常量類型
【必須】通常情況下踏兜,不要使用#define預處理命令(preprocessor command)創(chuàng)建常量词顾。正如上面所說,對于整型常量碱妆,使用枚舉創(chuàng)建肉盹;對于浮點型常量,使用const修飾符創(chuàng)建疹尾。
【必須】有些符號需要使用大寫字母標識上忍。預處理器需要根據這個符號進行計算以便決定是否要對某一塊代碼進行處理骤肛。比如:
#ifdef DEBUG
注意:那些編譯器定義的宏,左側和右側各有兩個下劃線睡雇。如下:
__MACH__
【必須】通知的名字和字典的key萌衬,應該使用字符串常量來定義。使用字符串常量編譯器可以進行檢查它抱,這樣可以避免拼寫錯誤。Cocoa 系統(tǒng)庫提供了許多字符串常量的例子朴艰,比如:
APPKIT_EXTERN NSString *NSPrintCopies;
字符串常量應該在.h頭文件中暴露給外部观蓄,而字符串常量真正的賦值是在.m文件中。如下:
.h文件
extern NSString *const WSNetworkReachablityStatusDidChangedNotification;
.m文件
NSString * const WSNetworkReachablityStatusDidChangedNotification = @"WSNetworkReachablityStatusDidChangedNotification";
(1.13) Exception命名規(guī)范
Notifications and Exceptions
上面已經有一節(jié)介紹過通知的命名規(guī)范祠墅。異常和通知的命名遵循相似的規(guī)則侮穿,但又各有不同。
【必須】和Notification的命名規(guī)范一樣(可參考Notification命名規(guī)范一節(jié))毁嗦,異常也是用全局的NSString字符串進行標識亲茅。命名方式如下:
[Prefix] + [UniquePartOfName] + Exception
相當于異常由前綴、名稱中能夠標識異常唯一性的那部分狗准、Exception克锣。如下:
NSColorListIOException
NSColorListNotEditableException
NSDraggingException
NSFontUnavailableException
NSIllegalSelectorException
(二)編碼規(guī)范
(2.1) Initialize規(guī)范
Tips and Techniques for Framework Developers
- (void)initialize類方法先于其他的方法調用。且initialize方法給我們提供了一個讓代碼once腔长、lazy執(zhí)行的地方袭祟。initialize通常被用于設置class的版本號(參考 Versioning and Compatibility)。
initialize方法的調用遵循繼承規(guī)則(所謂繼承規(guī)則捞附,簡單來講是指:子類方法中可以調用到父類的同名方法巾乳,即使沒有調用[super xxx])。如果我們沒有實現initialize方法鸟召,運行時初次調用這個類的時候胆绊,系統(tǒng)會沿著繼承鏈(類繼承體系),先后給繼承鏈上游中的每個超類發(fā)送一條initialize消息欧募,直到某個超類實現了initlialize方法压状,才會停止向上調用。因此槽片,在運行時何缓,某個類的initialize方法可能會被調用多次(比如,如果一個子類沒有實現initialize方法)还栓。
比如:有三個類:SuperClass碌廓、SubClass和FinalClass。他們的繼承關系是這樣的FinalClass->SubClass->SuperClass剩盒,現只實現了SuperClass方法的initialize方法谷婆。
// SuperClass
@implementation SuperClass
+ (void)initialize {
NSLog(@"superClass initalize");
}
@end
// 初始化FinalClass - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent *)event {
FinalClass *finalC = [FinalClass new];
}
// 控制臺輸出結果
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實現了initialize方法纪挎,導致初始化FinalClass這個子類時期贫,FinalClass會調用他的父類(SubClass)中的initialize方法。又因為他(FinalClass)的父類(SubClass)也沒有實現initialize方法异袄,又會繼續(xù)沿著繼承體系通砍,向上游尋找,最后找到SubClass的父類(SuperClass)烤蜕。因為SuperClass實現了這個initialize方法封孙,所以調用結束。至于為什么是連續(xù)調用了三次SuperClass的initialize方法讽营。因為子類FinalClass的初始化觸發(fā)了超類SubClass虎忌、SuperClass的初始化。所以初始化FinalClass時橱鹏,實際上使這三個類都得到了初始化的機會膜蠢,自然就會連續(xù)調用三次SuperClass的initialize方法。
還是上面那三個類莉兰,如果我們又給SubClass實現了initialize方法挑围,那么控制臺將會輸出如下結果(至于為什么,前面已經介紹過了贮勃,大家可以自己分析下):
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
基于上面陳述的這些事實贪惹,我們得出一個結論:
【必須】如果我們想要讓initialize方法僅僅被調用一次,那么需要借助于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
}
說了這么多泉孩,總而言之硼端,由于任何子類都會調用父類的initialize方法,所以可能會導致某個父類的initialize方法會被調用多次寓搬,為了避免這種情況珍昨,我們可以使用類型判等或dispatch_once()這兩種方式,以保證initialize中的代碼不會被無辜調用句喷。
initialize是由系統(tǒng)自動調用的方法镣典,我們不應該顯示或手動調用initialize方法。如果我們要觸發(fā)某個類的初始化行為唾琼,應該調用這個類的一些無害的方法兄春。比如:
[NSImage self];
(2.2 )Init方法規(guī)范
Objective-C有designated Initializers和secondary Initializers的概念。designated Initializers叫做指定初始化方法锡溯「嫌撸《Effective Objective-C 2.0 編寫高質量iOS 與 OS X代碼的52個有效方法》中將designated Initializers翻譯為”全能初始化方法“哑姚。designated Initializers方法是指類中為對象提供必要信息以便其能完成工作的初始化方法。一個類可以有一個或者多個designated Initializers芜茵。但是要保證所有的其他secondary initializers都要調用designated Initializers叙量。即:只有designated Initializers才會存儲對象的信息。這樣的好處是:當這個類底層的某些數據存儲機制發(fā)生變化時(可能是一些property的變更)九串,只需要修改這個designated Initializers內部的代碼即可绞佩。無需改動其他secondary Initializers初始化方法的代碼。
【必須】所有secondary 初始化方法都應該調用designated 初始化方法猪钮。
【必須】所有子類的designated初始化方法都要調用父類的designated初始化方法征炼。使這種調用關系沿著類的繼承體系形成一條鏈。
【必須】如果子類的designated初始化方法與超類的designated初始化方法不同躬贡,則子類應該覆寫超類的designated初始化方法。(因為開發(fā)者很有可能直接調用超類的某個designated方法來初始化一個子類對象眼坏,這樣也是合情合理的拂玻,但使用超類的方法初始化子類,可能會導致子類在初始化時缺失一些必要信息)宰译。
【必須】如果超類的某個初始化方法不適用于子類檐蚜,則子類應該覆寫這個超類的方法,并在其中拋出異常沿侈。
【必須】禁止子類的designated初始化方法調用父類的secondary初始化方法闯第。否則容易陷入方法調用死循環(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中調用 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的方式訪問屬性咳短。如果存在繼承的情況下,很有可能導致崩潰
(2.3) Init error
一個好的初始化方法應該具備以下幾個方面蛛淋,在初始化階段就能夠發(fā)現錯誤并給予處理咙好,也就是初始化方法應該具備一些必要的容錯功能。
【必須】調用父類的designated初始化方法初始化本類的對象褐荷。
【必須】校驗父類designated初始化方法返回的對象是否為nil勾效。
【建議】如果初始化當前對象的時候發(fā)生了錯誤,應該給予對應的處理:釋放對象叛甫,并返回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 方法應該放在實現文件的最上面其监,并且剛好在 @synthesize 和 @dynamic 語句的后面萌腿。在任何類中,init 都應該直接放在 dealloc 方法的下面棠赛。
【必須】在dealloc方法中哮奇,禁止將self作為參數傳遞出去膛腐,如果self被retain住,到下個runloop周期再釋放鼎俘,則會造成多次釋放crash哲身。如下:
-(void)dealloc{
[self unsafeMethod:self];
// 因為當前已經在self這個指針所指向的對象的銷毀階段,銷毀self所指向的對象已經在所難免贸伐。如果在unsafeMethod:中把self放到了autorelease poll中勘天,那么self會被retain住,計劃下個runloop周期在進行銷毀捉邢。但是dealloc運行結束后脯丝,self所指向的對象的內存空間就直接被回收了,但是self這個指針還沒有銷毀(即沒有被置為nil)伏伐,導致self變成了一個名副其實的野指針宠进。
// 到了下一個runloop周期,因為self所指向的對象已經被銷毀藐翎,會因為非法訪問而造成crash問題材蹬。
}
【必須】和init方法一樣,禁止在dealloc方法中使用self.xxx的方式訪問屬性吝镣。如果存在繼承的情況下堤器,很有可能導致崩潰
(2.5) Block規(guī)范
【必須】調用block時需要對block判空。
【必須】注意block潛在的引用循環(huán)末贾。
(2.6) Notification規(guī)范
前面在命名規(guī)范一章中已經介紹了通知的命名規(guī)范闸溃,這里解釋的是通知的使用規(guī)范。
通知作為觀察者模式的一個落地產物拱撵,在開發(fā)中能夠實現一對多的通信辉川。所有可以使用delegate和block實現的通信和傳值,都可以使用通知實現裕膀。正因通知如此靈活员串,我們更應該弄清楚通知適合使用的場景,避免把通知和delegate以及block等進行混淆昼扛。
通知是一把雙刃劍寸齐,讓你歡喜讓你憂。開發(fā)中抄谐,當你走投無路將要崩潰時渺鹦,可以考慮使用通知;而當你頻繁使用通知時蛹含,同樣會讓你崩潰到走投無路毅厚。所以,在每個應用中浦箱,我們應該時刻留意并控制通知的數量吸耿,避免通知滿天飛的現象祠锣。
曾經有一個項目擺在我面前,我卻無法珍惜咽安,因為通知太多了伴网,幾乎有代碼的地方就有通知。如果現在同樣有一個充滿通知的項目擺在我面前妆棒,我知道是時候該優(yōu)化它了澡腾。
【必須】基于以上的陳述,當我們使用通知時糕珊,必須要思考动分,有沒有更好的辦法來代替這個通知。禁止遇到問題就想到通知红选,把通知作為備選項而非首選項澜公。
【必須】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];
}
【必須】在多線程應用中,Notification
在哪個線程中post
贺喝,就在哪個線程中被轉發(fā)菱鸥,而不一定是在注冊觀察者的那個線程中。如果post
消息不在主線程躏鱼,而接受消息的回調里做了UI操作氮采,需要讓其在主線程執(zhí)行。
說明:每個進程都會創(chuàng)建一個NotificationCenter
染苛,這個center
通過NSNotificationCenter
defaultCenter
獲取鹊漠,當然也可以自己創(chuàng)建一個center。
NoticiationCenter
是以同步(非異步茶行,當前線程躯概,會等待,會阻塞)的方式發(fā)送請求畔师。即娶靡,當post通知時,center
會一直等待所有的observer
都收到并且處理了通知才會返回到poster
看锉。如果需要異步發(fā)送通知姿锭,請使用notificationQueue
塔鳍,在一個多線程的應用中,通知會發(fā)送到所有的線程中呻此。
(2.7) UI規(guī)范
【必須】如果想要獲取window
轮纫,不要使用view.window
獲取。請使用[[UIApplication sharedApplication] keyWindow]
趾诗。
【必須】在使用到 UIScrollView
蜡感,UITableView
,UICollectionView
的 Class
中恃泪,需要在 dealloc 方法里手動的把對應的 delegate
, dataSouce
置為 nil
郑兴。
【必須】UITableView
使用self-sizing
實現不等高cell
時,請在- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
中給cell
設置數據贝乎。不要在- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;
方法中給cell
設置數據情连。
【建議】當訪問一個CGRect
的x
,y
览效,width
却舀,height
時,應該使用CGGeometry函數代替直接訪問結構體成員锤灿。蘋果的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
住當前線程但校,知道所有的內容都寫進磁盤螃诅,如果內容過多,重復調用的話會嚴重影響性能状囱。
【建議】一些經常被使用的文件建議做好緩存术裸。避免重復的IO操作。建議只有在合適的時候再進行持久化操作亭枷。
(2.9) Collection規(guī)范
【必須】不要用一個可能為nil的對象初始化集合對象袭艺,否則可能會導致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]’
【必須】同理叨粘,對插入到集合對象里面的對象也要進行判空猾编。
【必須】注意在多線程環(huán)境下訪問可變集合對象的問題,必要時應該加鎖保護升敲。不可變集合(比如NSArray)類默認是線程安全的袍镀,而可變集合類(比如NSMutableArray)不是線程安全的。
【必須】禁止在多線程環(huán)境下直接訪問可變集合對象中的元素冻晤。應該先對其進行copy苇羡,然后訪問不可變集合對象內的元素。
// 正確寫法
- (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遍歷集合對象中的對象時锦茁,關鍵字return的作用域。block中的return代表的是使當前的block返回叉存,而非使當前的整個函數體返回码俩。以下使用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;
}];
// 依然會執(zhí)行到這里
NSLog(@"fall through");
}
// 執(zhí)行結果:
// fall through
當然稿存,兩個enumerateObjectsUsingBlock嵌套,如果僅在最內層的block中return瞳秽,外層block的代碼還是會被執(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í)行結果:
// fall through
// fall through
說明:其實block相當于一個匿名函數,在block中使用return返回练俐,僅是讓當前這個匿名函數返回袖迎。
【必須】禁止返回mutable對象,禁止mutable對象作為入參傳遞腺晾。
【建議】如果使用NSMutableDictionary作為緩存燕锥,建議使用NSCache代替。
(2.10) 分支語句規(guī)范
【建議】if條件判斷語句后面必須要加大括號{}悯蝉。不然隨著業(yè)務的發(fā)展和代碼迭代归形,極有可能引起邏輯問題。
// 建議
if (!error) {
return success;
}
// 不建議
if (!error)
return success;
if (!error) return success;
【必須】多于3個邏輯表達式必須用參數分割成多個有意義的bool變量鼻由。
【建議】遵循gold path法則魏颓,不要把真正的邏輯寫道括號內渐行。
// 不建議
- (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關鍵字蜜自,避免出現fall-through。
(2.11) 對象判等規(guī)范
isEqual:
方法允許我們傳入任意類型的對象作為參數卢佣,如果參數類型和receiver
(方法調用者)類型不一致重荠,會返回NO
。而isEqualToString:
和isEqualToArray:
這兩個方法會假設參數類型和receiver
類型一致虚茶,也就是說戈鲁,這兩個方法不會對參數進行類型檢查仇参。因此這兩個方法性能更好但不安全。如果我們是從外部數據源(比如info.plist或preferences)獲取的數據婆殿,那么推薦使用isEqual:
诈乒,因為這樣更安全。如果我們知道參數的確切類型婆芦,那么可以使用類似于isEqualToString:
這樣的方法怕磨,因為性能更好。
(2.12) 懶加載規(guī)范
懶加載適合的場景:
- 一個對象的創(chuàng)建依賴于其他對象消约。
- 一個對象在整個app過程中肠鲫,可能被使用,也可能不被使用荆陆。
- 一個對象的創(chuàng)建需要經過大量的計算或者比較消耗性能滩届。除以上三條之外,請不要使用懶加載被啼。
【建議】懶加載本質上就是延遲初始化某個對象帜消,所以,懶加載僅僅是初始化一個對象浓体,然后對這個對象的屬性賦值泡挺。懶加載中不應該有其他的不必要的邏輯性的代碼,如果有命浴,請把那些邏輯性代碼放到合適的地方娄猫。
【必須】不要濫用懶加載,只對那些真正需要懶加載的對象采用懶加載生闲。
【必須】如果一個對象在懶加載后媳溺,某些場景下又被設置為nil。我們很難保證這個懶加載不被再次觸發(fā)碍讯。
(2.13) 多線程規(guī)范
【必須】禁止使用GCD的dispatch_get_current_queue()函數獲取當前線程信息悬蔽。
【必須】對剪貼板的讀取必須要放在異步線程處理,最新Mac和iOS里的剪貼板共享功能會導致有可能需要讀取大量的內容捉兴,導致讀取線程被長時間阻塞蝎困。
【建議】僅當必須保證順序執(zhí)行時才使用dispatch_sync,否則容易出現死鎖倍啥,應避免使用禾乘,可使用dispatch_async。
- (void)viewDidLoad {
[super viewDidLoad];
// 錯誤虽缕。出現死鎖始藕,報錯: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í)行鳄虱。雖然還是把任務加到了主隊列由主線程來執(zhí)行弟塞,但因為是異步,此時主隊列后面的任務不依賴于前面的任務拙已。
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_block_t block = ^() {
NSLog(@"%@", [NSThread currentThread]);
};
dispatch_async(mainQueue, block);
}
// 打印結果:
// {number = 1, name = main}
【必須】禁止在非主線程中進行UI元素的操作决记。
【必須】在主線程中禁止進行同步網絡資源讀取,使用NSURLSession
進行異步獲取倍踪。當然系宫,你可以在子線程同步獲取網絡資源,但還是上面的那一條建議:避免使用dispatch_sync
建车,盡量使用dispatch_async
扩借。因為死鎖不一定只發(fā)生在主線程。
【必須】如果需要進行大文件或者多文件的IO操作缤至,禁止主線程使用潮罪,必須進行異步處理。
【必須】對剪貼板的讀取必須要放在異步線程處理领斥,最新Mac和iOS里的剪貼板共享功能會導致有可能需要讀取大量的內容嫉到,導致讀取線程被長時間阻塞。
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) 內存管理規(guī)范
【建議】函數體提前return時月洛,要注意是否有對象沒有被釋放掉(常見于CF對象)何恶,避免造成內存泄露。
【建議】請慎重使用單例嚼黔,避免產生不必要的常駐內存细层。
說明:我們不僅應該知道單例的特點和優(yōu)勢,也必須要弄明白單例適合的場景唬涧。UIApplication
疫赎、access database
、request network
碎节、access userInfo
這類全局僅存在一份的對象或者需要多線程訪問的對象捧搞,可以使用單例。不要僅僅為了訪問方便就使用單例钓株。
【建議】單例初始化方法中盡量保證單一職責,尤其不要進行其他單例的調用。極端情況下陌僵,兩個單例對象在各自的單例初始化方法中調用轴合,會造成死鎖。
【必須】在dealloc
方法中碗短,禁止將self作為參數傳遞出去受葛,如果self
被retain
住,到下個runloop
周期再釋放,則會造成多次釋放crash
总滩。這一點在dealloc
一節(jié)中有說明纲堵。
【建議】除非你清除的知道自己在做什么。否則不建議將UIView
類的對象加入到NSArray
闰渔、NSDictionary
席函、NSSet
中。如有需要可以添加到NSMapTable
和 NSHashTable
冈涧。因為NSArray
茂附、NSDictionary
、NSSet
會對加入的對象做strong
引用(即使你把加入的對象進行了weak
)督弓。而NSMapTable
营曼、NSHashTable
會對加入的對象做weak
引用。
說明:簡單的說愚隧,NSHashTable
相當于weak
的NSMutableArray
蒂阱;NSMapTable
相當于weak
的NSMutableDictionary
.
// 錯誤的例子:
@implementation WSObject
- (void)dealloc {
NSLog(@"dealloc");
}
@end
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
WSObject *object = [WSObject new];
// 即使對object進行了weak弱化,數組也會強引用這個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);
});
}
// 打印結果:
// 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);
});
}
// 打印結果:
// dealloc
// count = 1
你可能對上面的例子有所疑惑,object已經釋放了睹耐,但是控制臺仍然輸出 hashTable.count == 1辐赞。但是請相信我,此時存在于hashTable中的那個object已經變成了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]);
}
});
}
// 打印結果:
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) 延遲調用規(guī)范
【必須】performSelector:withObject:afterDelay:
要在有Runloop
的線程里調用响委,否則調用無法生效。
說明:異步線程默認是沒有runloop
的窖梁,除非手動創(chuàng)建赘风;而主線程是系統(tǒng)會自動創(chuàng)建Runloop
的。所以在異步線程調用是請先確保該線程是有Runloop
的纵刘。
使用performSelector:withObject:afterDelay:
和cancelPreviousPerformRequestsWithTarget
組合的時候要小心:
afterDelay
會增加引用計數邀窃,而cancel
會對引用計數減一
如果receiver
在引用計數器為1的時候,調用cancel
會立即回收receiver
假哎。后續(xù)再次調用receive
的方法就會crash
瞬捕。所以我們需要使用weakSelf
并判空。如下:
__weak typeof(self) weakSelf = self;
[NSObject cancelPreviousPerformRequestsWithTarget:self];
if (!weakSelf) {
// NSLog(@"self dealloc");
return;
}
[self doOther];
(2.16) 注釋規(guī)范
【必須】如果方法舵抹、函數肪虎、類、屬性等需要提供給外界或者他人使用惧蛹,必須要加注釋說明扇救。
【必須】如果你的代碼以SDK的形式提供給其他人使用刑枝,那么接口的注釋是必須的。必須對暴露給外界的所有方法迅腔、屬性装畅、參數加以注釋說明。
【建議】注釋應該說明其作用以及注意事項(如果有)沧烈。
【建議】因為方法或屬性本身就具有自我描述性掠兄,注釋應該簡明扼要,說明是什么和為什么即可掺出。
(2.17) 類的設計規(guī)范
【建議】盡量減少繼承徽千,類的繼承關系不要超過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) 工程結構規(guī)范
【必須】為了避免文件雜亂,物理文件應該保持和 Xcode 項目文件同步害晦。Xcode 創(chuàng)建的任何組(group)都必須在文件系統(tǒng)有相應的映射特铝。為了更清晰,代碼不僅應該按照類型進行分組壹瘟,也可以根據業(yè)務功能進行分組鲫剿。
【建議】合理組織工程的內的文件夾,工程中一般包括但不限于以下幾個文件夾category(分類)稻轨、util/helper(工具類)灵莲、resource(資源)、const(常量)澄者、third(第三方)笆呆。
【建議】盡可能一直打開 target Build Settings 中 "Treat Warnings as Errors" 以及一些額外的警告。如果你需要忽略指定的警告,使用 Clang 的編譯特性 粱挡。
轉載:文/VV木公子(簡書作者)http://www.reibang.com/p/c818c00e0690
轉載請聯(lián)系作者獲得授權赠幕,并注明出處。