介紹
? 代碼是寫給程序員看的,所以代碼命名需要一個約定俗成的規(guī)范,否則接口就會有歧義厕氨,別人則會因此被迷惑從而增加閱讀进每、溝通成本。本文就是為了教你寫出更通俗易懂的接口命斧。
? 引用:Introduction to Coding Guidelines for Cocoa
代碼基礎(chǔ)命名規(guī)范
這一章主要展示OOP(面向?qū)ο缶幊蹋┤菀妆蝗撕鲆暤囊恍╆P(guān)于類田晚、方法、函數(shù)国葬、常量以及編程接口的其他元素的命名肉瓦。
基本原則
-
清晰明了
- 不能因為貪圖簡潔而影響了接口的明確性
-insertObject:atIndex: // good -insert:at: // bad
- 盡可能不要用單詞縮寫,除非這個縮寫是眾所周知的胃惜,例如:max、min哪雕、app船殉、func、temp等等斯嚎。
-setBackgroundColor: // good -setBgColor: //bad
- 不能引起歧義
-sendPort // 不知道是發(fā)送到端口還是返回這個端口 -displayName // 不知道是展示一個名字還是返回一個需要展示的名字
-
一致性
- 盡量使用cocoa框架中出現(xiàn)過的名稱利虫,當(dāng)你定義了一個具有多態(tài)性的類時接口保持一致性尤為重要。
- (NSInteger)tag // NSView堡僻,NSCell糠惫,NSControl
-
不引用自己
- 命名不應(yīng)該自己引用自己
NSString // good NSStringObject // bad
- 可按位運算的常量(
NS_OPTIONS
)與通知是例外
UIViewAutoresizingFlexibleWidth // good UIKeyboardWillShowNotification // good
前綴
? 前綴的重要性不言而喻,尤其是當(dāng)你不得不使用其他人些的一些框架的時候钉疫,包括蘋果的框架和第三方框架硼讽。前綴可以幫助我們避免沖突,例如類重復(fù)定義牲阁,分類方法命名重復(fù)等等...
- 前綴有個規(guī)定的格式:由2-3個大寫字母組成固阁,注意萬萬不能使用下劃線
- 命名類、協(xié)議城菊、函數(shù)备燃、常量和
typedef
結(jié)構(gòu)體的時候可以使用前綴,但是命名方法不需要使用到前綴凌唬,因為方法只存在于類的命名空間中并齐,不同的類有重名的方法實屬正常。順便說一下客税,結(jié)構(gòu)體中的字段也不必要使用前綴
書寫約定
- 不要使用標(biāo)點符號况褪、下劃線、破折號等等霎挟,應(yīng)采用駝峰標(biāo)識來命名窝剖。如
setBackgroundColor:
- 方法名:首字母小寫,不用前綴酥夭。如
fileExistsAtPath:isDirectory:
- 類名:前綴+首字母大寫赐纱。如
UITableView
- 方法名:首字母小寫,不用前綴酥夭。如
- 避免以下劃線作為開頭來命名方法以表示這方法為私有方法脊奋,不過允許以下劃線開頭來命名實例變量以示其為私有變量。蘋果保留這種使用約定疙描,也就是說你要是寫了一個以下劃線開頭的方法诚隙,很可能在未來某一天會把蘋果內(nèi)部的方法給重載掉而引起災(zāi)難性的后果(是的,蘋果就是這么說的)起胰。而私有方法的命名規(guī)范在下面會講到
類和協(xié)議命名規(guī)范
? 類名必須能從名稱中一目了然這個類是干什么的久又,并且應(yīng)該帶有前綴。Foundation
框架中的類處處可見效五,就不舉例子了地消。
? 協(xié)議則應(yīng)根據(jù)這一組的行為來命名枢赔。
? 大部分協(xié)議定義了一系列與類無關(guān)的方法作煌,這種協(xié)議需要從命名上就與類區(qū)分開壮啊,最普遍的就是使用動名詞的方法("…ing")
NSLocking // good
NSLock // bad朽缴,聽起來就像一個類
? 而有一些協(xié)議定義了一些列互不關(guān)聯(lián)的方法枕赵,這種協(xié)議往往作為與協(xié)議主要表達(dá)式的類關(guān)系緊密传透。這種情況的話協(xié)議名就跟類名保持一致机杜。最經(jīng)典的例子就是NSObject
與<NSObject>
饼疙。
頭文件
? 頭文件名字代表著這個文件里面包含著什么東西迅细。
- 定義一個孤立的類或者協(xié)議巫橄,除非這些類或者協(xié)議就很強的關(guān)聯(lián)性,否則請把他們放在不同的頭文件里
NSLocale.h // NSLocale這個類的聲明茵典,沒別的了
- 定義一系列強關(guān)聯(lián)性的類或者協(xié)議湘换,請將他們的定義放在主類、協(xié)議敬尺、分類的頭文件中
NSLock.h // NSLocking協(xié)議枚尼,以及NSLock、NSConditionLock砂吞、NSRecursiveLock這幾個類的聲明
- 包含框架中向外暴露的所有頭文件署恍,命名得跟框架名一樣。
Foundation.h // Foundation.framework
- 給另一個框架里的類加API蜻直。例如給NSString加一個分類盯质,請在原類名后面拼接上"Additions"。如
NSBundleAdditions.h
- 相關(guān)的一些方法和數(shù)據(jù)類型概而。如果你有一組相關(guān)的函數(shù)呼巷、常量、結(jié)構(gòu)體或其他數(shù)據(jù)類型等赎瑰,將他們放在同一個頭文件中然后給這個頭文件取一個恰當(dāng)?shù)拿?/li>
方法命名規(guī)范
? 這一節(jié)主要是講如何正確命名我們平時打碼中隨處可見的元素 - Method王悍。
通用規(guī)則
? 以下有幾條在給方法命名的時候必須牢記的準(zhǔn)則:
-
首字母小寫,然后接著的每個單詞的首字母大寫餐曼。不要給方法名加前綴压储。
- 有兩個例外鲜漩,第一是如果你要描述的這個單詞是眾所周知的單詞縮寫,例如TIFF of PDF這種集惋,則可以全大寫孕似。第二是當(dāng)你需要用前綴去給私有方法分組歸類方便區(qū)分的時候可以使用前綴
如果方法代表的是一個動作,那么請使用動詞作為方法名的開頭
- (void)invokeWithTarget:(id)target; - (void)selectTabViewItem:(NSTabViewItem *)tabViewItem; // PS: 不要用 'do' 或者 'does' 表示動作, 并且永遠(yuǎn)不要在動詞前加形容詞(adj.)或者副詞(adv.)
如果該方法返回一個屬性刮刑,那么直接用這個屬性來明明即可喉祭。另外除非該方法可返回多個值,否則沒必要在方法名前面加'get'
- (CGSize)cellSize; // good - (CGSize)calculateCellSize; // bad - (CGSize)getCellSize; // bad
在所有方法參數(shù)前用關(guān)鍵字表示
- (void)sendAction:(SEL)aSelector toObject:(id)anObject forAllCells:(BOOL)flag; // good - (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag; // bad
在參數(shù)前用一個單詞來簡單形容這個參數(shù)是干嘛用的
- (id)viewWithTag:(NSInteger)aTag; // good - (id)taggedView:(int)aTag; // bad
當(dāng)你創(chuàng)建一個比你所繼承的方法更為具體的一個方法時雷绢,在方法末尾新增一個關(guān)鍵詞即可泛烙,這種情況在UIKit很常見
- (id)initWithFrame:(CGRect)frame; - (id)initWithFrame:(CGRect)frame mode:(int)mode; - (id)initWithFrame:(CGRect)frame mode:(int)mode ...
不要使用'and'來連接兩個兩個方法參數(shù)
- (int)runModalForDirectory:(NSString *)path file:(NSString *) name types:(NSArray *)fileTypes; // good - (int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes; // bad // PS: 當(dāng)你的方法有多個參數(shù)時,用 'and' 就顯得非常非常啰嗦
如果方法描述的是兩個獨立的操作翘紊,這時候可以用'and'來連接
- (BOOL)openFile:(NSString *)fullPath withApplication:(NSString *)appName andDeactivate:(BOOL)flag; // NSWorkspace // PS: 這種情況一般比較少見
存取方法(Accessor Methods)
? 簡單地說胶惰,存取方法即set方法和get方法
-
屬性為名詞時
- (Type)noun; - (void)setNoun:(Type)noun; // example - (NSString *)title; - (void)setTitle:(NSString *)title;
-
屬性為形容詞時
- (BOOL)isAdjective霞溪; - (void)setAdjective:(BOOL)flag; // example - (BOOL)isEditing; - (void)setEditing:(NSString *)editing;
-
屬性為動詞時
- (BOOL)verbObject; - (void)setVerbObject:(BOOL)flag; // example, 動詞為一般現(xiàn)在時 - (BOOL)showsAlpha; - (void)setShowsAlpha:(BOOL)showsAlpha;
-
不要把動詞變成形容詞
- (BOOL)acceptsGlyphInfo; // good - (BOOL)glyphInfoAccepted; // bad
-
可使用情態(tài)動詞(如 can中捆、should鸯匹、will 等等)來闡明意思,但永遠(yuǎn)不要用do or does
- (void)setCanHide:(BOOL)flag; // good - (void)setShouldCloseDocument:(BOOL)flag; // good - (void)setDoesAcceptGlyphInfo:(BOOL)flag; // bad
-
當(dāng)方法可能返回多個參數(shù)時泄伪,使用get為方法前綴
- (void)getLineDash:(float *)pattern count:(int *)count phase:(float *)phase; // PS: 這種情況下方法實現(xiàn)中殴蓬,參數(shù)應(yīng)該可以接收 NULL,因為調(diào)用方可能并不在意某些參數(shù)的返回值是多少
代理方法(Delegate Methods)
-
以發(fā)送消息的某類的對象作為方法名的開頭
- (BOOL)tableView:(NSTableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath; - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)options; // PS: 類名省略前綴蟋滴,并以小寫字母開頭染厅。如UIApplication -> application
-
除非方法只有一個參數(shù)(即發(fā)送者),否則類名會緊跟在冒號后面(參數(shù)是代理對象的引用)
- (void)applicationDidEnterBackground:(UIApplication *)application;
-
有一種特殊情況津函,如果代理方法是用來統(tǒng)一處理通知的話肖粮,該方法的唯一參數(shù)就是通知本身
- (void)windowDidChangeScreen:(NSNotification *)notification;
-
使用
will
或者did
來告知代理某些行為將要或者已經(jīng)發(fā)生- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView; - (void)scrollViewDidScroll:(UIScrollView *)scrollView;
-
你可以使用
should
來詢問代理自己是否需要做某些事情- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView;
集合類型方法(Collection Methods)
? 對于管理一個集合的對象,這里直接說容器了哈尔苦。約定好方法的形式如下:
- (void)addElement:(ElementType)element;
- (void)removeElement:(ElementType)element;
- (NSArray *)elements;
以下是一些細(xì)化的準(zhǔn)則:
如果集合是無序的涩馆,返回
NSSet
比NSArray
更好-
對于插入/刪除元素這種行為,應(yīng)該提供類似上述方法的API允坚,如:
- (void)insertElement:(ElementType)element atInex:(NSUInteger)index; - (void)removeElementAtIndex:(NSUInteger)index;
方法參數(shù)(Method Arguments)
直接上注意點吧:
- 參數(shù)名小寫字母開頭魂那,遵循駝峰命名法
- 盡量用參數(shù)的類型去告訴別人這個參數(shù)是不是指針,而不是用參數(shù)名
- 盡量不要用1~2個字母作為參數(shù)名
- 盡量不要縮寫到只剩幾個字母
私有方法(Private Methods)
? 大部分情況下稠项,私有方法命名跟公有方法是一樣的涯雅。有個很簡便的方法去區(qū)分私有方法就是給他加一個前綴。但是有個弊端就是展运,你不知道會不會無意中重寫了蘋果框架中的私有方法活逆。精刷。。
? 但是蘋果框架中的私有方法大部分都是以下劃線作為前綴的划乖。所以我們就盡量不要用下劃線作為前綴明明我們自己的方法了贬养。另外如果我們子類化一個使用特別廣泛的類例如UIView
,那么我們給自己的類加私有方法的時候可以用上獨一無二的前綴琴庵,例如公司名+項目名XX_addObject:
误算。
函數(shù)命名規(guī)范
? 函數(shù)命名我們需要遵循以下幾個規(guī)范:
-
函數(shù)命名有點像方法命名,但是有一點不同:
- 他們以你用于類名或常量的相同前綴開頭
- 前綴后面緊跟著的第一個字母總是大寫
-
大部分函數(shù)以說明該函數(shù)有什么用的動詞作為函數(shù)名開頭
void NSDeallocateObject(id object);
查詢屬性的函數(shù)還有一組命名規(guī)則:
-
如果函數(shù)返回的是他的第一個參數(shù)的屬性迷殿,可省略動詞
float NSHeight(NSRect aRect); // CGFloat CGRectGetWidth(CGRect rect); // UIKit下不符合這個規(guī)范
-
如果返回的是引用儿礼,使用
Get
const char *NSGetSizeAndAlignment(const char *typePtr, unsigned int *sizep, unsigned int *alignp);
-
如果返回值是布爾值的話,函數(shù)名應(yīng)以變形后的動詞開頭
BOOL NSDecimalIsNotANumber(const NSDecimal *dcm);
以上這三個規(guī)則說實話我感覺有點過時了庆寺。蚊夫。。大家看看就好
屬性以及數(shù)據(jù)類型命名規(guī)范
? 這一節(jié)講的是屬性懦尝、實例變量知纷、常量、通知以及異常的命名規(guī)范陵霉。
屬性和實例變量
? 命名屬性跟前面講過的存取方法很類似琅轧,忘記了的話可以回去看看哦,舉個例子應(yīng)該就能馬上回想起來了:
@property (nonatomic, strong) NSString *title;
@property (nonatomic, assign) BOOL showsAlpha;
如果屬性表達(dá)的是形容詞的話踊挠,省略is
然后在getter方法里體現(xiàn)出來:
@property (nonatomic, assign, getter=isEditable) BOOL editable;
絕大部分情況下乍桂,你聲明一個屬性相當(dāng)于同時合成(synthesize)了相應(yīng)的實例變量, 例如:
@implementation MyClass {
BOOL _showsAlpha;
}
你也可以手動加上@synthesize
去決定相應(yīng)的實例變量名效床,如:
@implementation MyClass
@synthesize showsAlpha = myShowsAlpha // 此時生成的實例變量名就為myShowsAlpha而不再是_showsAlpha了睹酌,一般不建議這樣用
有幾點需要注意的是:
- 避免明確聲明實例變量。開發(fā)者只需關(guān)心屬性剩檀,接口即可憋沿,具體是以什么實例變量去存儲的開發(fā)者不需要理會,正常情況下只需要讓系統(tǒng)給我們自動生成的實例變量即可
- 如果確實需要聲明實例變量沪猴,請明確使用
@private
或@protected
來聲明它卤妒。尤其是如果你確信你的類會被其他子類繼承,那么子類會需要訪問到該實例變量的話字币,使用@protected
即可 - 如果該實例變量是該類實例的可訪問屬性则披,請確保你已經(jīng)為他寫了存取方法,如果可以的話還是請聲明為屬性
@property
吧
常量
? 常量怎么命名往往取決于這個常量是如何被創(chuàng)建的
枚舉常量
? 一般情況下洗出,我們會使用枚舉給一系列整形常量分組士复。具體命名規(guī)范和命名函數(shù)差不多。大家可以回頭去看看,這里給一個例子
typedef enum _NSMatrixMode { // 這個_NSMatrixMode可省略
NSRadioModeMatrix = 0,
NSHighlightModeMatrix = 1,
NSListModeMatrix = 2,
NSTrackModeMatrix = 3
} NSMatrixMode; // 甚至連這個NSMatrixMode都可以省略
帶const修飾符的變量
? 可用const
修飾符去創(chuàng)建一個跟其他常量沒有任何聯(lián)系的不可變常量阱洪,例如:
const float NSLightGray;
其他類型的常量
一般情況下便贵,不要用
#define
去定義常量,integer
常量就用枚舉去解決冗荸,float
常量就用const
去解決-
全大寫字母的預(yù)編譯指令
#ifdef DEBUG // code will be processed #endif
編譯器定義的宏前后都有雙下劃線承璃,例如
__MACH__
-
字符串常量的話常常被用作Notification Name或者Dictionary Key,需要注意的是在編譯器該字符串的值就需要被確定下來蚌本。例如
// .h UIKIT_EXTERN NSString *kViewDidShowNotification; // .m NSString *kViewDidShowNotification = @"kViewDidShowNotification";
通知和異常
? 通知和異常都有類似的命名規(guī)范盔粹。請往下看:
通知
? 一般來說,如果一個類有代理(delegate)的話程癌,他的通知都能找到其對應(yīng)的代理方法舷嗡。通知的命名一般包括以下幾個部分:
[Name of associated class] + [Did | Will] + [UniquePartOfName] + Notification
? 例如:UIApplicationDidBecomeActiveNotification
異常
? 異常一般如此命名:
[Prefix] + [UniquePartOfName] + Exception
? 例如:NSDraggingException
常見的縮寫及首字母縮寫(Acceptable Abbreviations and Acronyms)
? 一般情況下不建議在寫代碼的時候使用英文單詞縮寫,除非是一些人盡皆知的或者前人已經(jīng)使用過的縮寫嵌莉。例如进萄,alloc
,init
锐峭,app
等等中鼠。另外還有一些名詞如果不用縮寫的話可能人們看了也不知道為何物,例如沿癞,XML
兜蠕,JPG
,PNG
等等抛寝。這種情況下就建議直接用縮寫了。
對SDK開發(fā)者的提示和技巧
? 技巧都是通用的曙旭,不僅僅針對SDK開發(fā)者盗舰,可以運用在日常開發(fā)中。
初始化
? 以下開始介紹SDK的初始化的建議桂躏。
類初始化(Class Initialization)
? +initialize
方法提供一個地方可以給你以懶加載的形式(即該類運行時首次被使用到的時候)執(zhí)行某些一次性代碼钻趋。一般來說會在這里設(shè)置版本號。
? Runtime
會自動幫我們調(diào)用繼承鏈上每一個類的+initialize
方法剂习,即便你沒有實現(xiàn)他蛮位,并且當(dāng)子類沒有實現(xiàn)該方法時會默認(rèn)調(diào)用父類方法,所以該方法可能調(diào)用不止一次鳞绕。如果想要保證寫在這里的代碼整個應(yīng)用生命周期只會調(diào)用一起的話失仁,請使用dispatch_once()
。
+ (void)initialize {
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
// the initializing code
}
}
Note:
由于Runtime會幫我們調(diào)用每一個類的initialize方法们何,所以如果某子類沒有實現(xiàn)initialize方法的話萄焦,就會發(fā)生在子類的上下文里調(diào)用父類的initialize方法的。所以如果想要在對應(yīng)的上下文里實現(xiàn)相應(yīng)的initialize方法,可以在方法里面判斷一下類型拂封,這種方法比dispatch_once更好茬射。
if (self == [NSFoo class]) { // the initializing code }
? 永遠(yuǎn)不要自己去調(diào)用+initialize
方法,如果你想要觸發(fā)這個方法的話冒签,隨意調(diào)用一個沒有副作用的方法即可在抛,例如
[NSImage self];
指定的初始化器(Designated Initializers)
? Designated Initializers
簡單地說就是一個類的init
(假設(shè)init
方法就是該類的Designated Initializers
)方法會默認(rèn)調(diào)用其父類的init
方法,然后該類的其他初始化方法內(nèi)部都會調(diào)用該類的init
方法萧恕。例如UIView
的-initWithFrame:
方法刚梭。但是如果你的類是類似NSSring
和其他面向類族集Class clusters
的抽象類,并且不想覆蓋init
方法廊鸥,那么子類應(yīng)該實現(xiàn)自己的初始化方法望浩。
? Designated Initializers
應(yīng)該被清楚地標(biāo)識出來,因為如果其他類想要繼承自你這個類的話惰说,他只需要重寫這個 Designated Initializers
即可讓其他初始化方法都按照設(shè)計完成初始化工作磨德。
? 當(dāng)你在實現(xiàn)SDK中的類時,通常需要實現(xiàn)其歸檔和解檔方法吆视,-initWithCoder:
和-encodeWithCoder:
典挑。需要注意的是,不要在解檔期間去做任何事情啦吧。如果你的類實現(xiàn)了<NSCoding>
協(xié)議您觉,那么最好在你的Designated Initializers
中調(diào)用-initWithCoder
方法(-initWithCoder
本身也是Designated Initializers
)。
初始化期間的錯誤檢測
? 一個好的設(shè)計的初始化方法應(yīng)該完成以下3個步驟確保正確地檢測和傳播錯誤:
- 調(diào)用
super
的指定的初始化器后給self
重新賦值 - 檢查
self
是否為nil
授滓,這一步主要檢測調(diào)用super
的初始化方法是否成功 - 如果在初始化過程中發(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;
}
版本控制和兼容性
? 通常來說般堆,當(dāng)你為你的SDK加了新方法在孝、類時,沒必要為每個新功能組指定一個新的版本號淮摔。開發(fā)正可以通過OC的runtime方法respondsToSelector:
去驗證在指定系統(tǒng)下該方法是否可用私沮。
? 但是你可以采用多種技術(shù)手段去確保SDK的每個新版本都記性正確標(biāo)號,并且提高對舊版本的兼容性和橙。
SDK版本號
? 如果沒辦法簡單地通過runtime tests的方式去檢測新功能或bug修復(fù)的存在仔燕,那么則應(yīng)該為開發(fā)人員提供方式來檢查更改。一種方法就是存儲SDK的版本號魔招,然后開發(fā)人員可以根據(jù)這個版本號去查相應(yīng)資料:
- 為每個版本號編寫相應(yīng)的文檔晰搀,注明release note
- 把版本號存儲在某個地方并提供可訪問的方法。例如存在SDK的
info.plist
文件里
鍵控存檔(Keyed Archiving)
? 如果SDK中的對象需要被寫入nib
文件中办斑,那么他們必須自己能夠歸檔厕隧。除此以外,你還需要利用歸檔機(jī)制去歸檔所有文檔數(shù)據(jù)。
? 有以下幾點需要注意:
- 如果忘了將某個key歸檔吁讨,那么在取值的時候會得到
nil
髓迎、NULL
、NO
建丧、0
或者0.0
排龄。取決于API的返回值類型。因此翎朱,測試這些返回值能夠幫助你減少錯誤橄维,此外,還可以找出問題所在拴曲。 - 歸檔和解檔的方法都應(yīng)該確保能夠向后兼容争舞。例如,新版本的歸檔方法里會寫入新的鍵值對澈灼,同時也會寫入舊的key-value竞川,老版本依然能夠識別出這些老的鍵值對。因此叁熔,解檔方法里面需要以合理的方式去處理這些缺失值委乌,以及將來的版本里保持一定的靈活性。
- 一個推薦的歸檔key命名規(guī)范是使用SDK的API前綴+實例對象的名稱荣回。確保任何父類和子類的命名都不會沖突即可遭贸。
- 如果你有一個工具方法輸出的是基本數(shù)據(jù)類型(換言之,不是對象)心软,需要確保使用唯一鍵壕吹。例如用于歸檔矩形
archiveRect
應(yīng)該使用一個鍵參數(shù)并且使用一個給定的鍵,或者如果他寫出的是多個值(例如删铃,四個浮點數(shù))耳贬,則應(yīng)將自己的唯一位拼接到提供的鍵里面(PS:這個我直譯過來讀不太懂...) - 由于編譯器和字節(jié)序的依賴性,按原樣存檔位域可能很危險泳姐。 出于性能方面的考慮,僅當(dāng)需要多次寫入許多位時才應(yīng)對它們進(jìn)行歸檔暂吉。
異常和錯誤
? 大多數(shù)Cocoa的框架都不強制開發(fā)者去捕捉并處理異常胖秒。因為異常不會在正常使用過程中拋出,并且通常不用于傳達(dá)預(yù)期的運行時或用戶錯誤慕的。這些錯誤的例子包括:
- 文件未找到
- 沒有這樣的用戶
- 嘗試打開一個錯誤類型的文檔
- 將字符串轉(zhuǎn)為指定編碼時出錯
然而阎肝,當(dāng)出現(xiàn)變成或邏輯錯誤時Cocoa會拋出異常,例如:
- 數(shù)組索引超出范圍
- 嘗試改變一個不可變的對象
- 錯誤參數(shù)類型
理想的情況下肮街,開發(fā)者在應(yīng)用上線之前的測試環(huán)節(jié)中就能捕捉到這些類型的異常并且解決掉风题,因此,應(yīng)用無需在運行時處理異常。如果一個異常拋出并且應(yīng)用沒有去捕捉他時沛硅,最頂層的默認(rèn)handler將會捕捉并上報這些異常然后繼續(xù)執(zhí)行程序眼刃。開發(fā)者可以選擇自己去捕捉并處理這些異常,提供選項讓用戶保存數(shù)據(jù)并退出應(yīng)用程序摇肌。
錯誤是Cocoa的框架魚其他軟件庫的一方面擂红。Cocoa的方法一般不返回錯誤代碼。如果存在一個合理的或者類似錯誤的原因围小,則該方法依賴對布爾值或者對象是否為空的簡單判斷昵骤;并且在文檔中應(yīng)能夠查到返回NO或者nil的原因。你不應(yīng)使用錯誤代碼來指示要在運行時處理編程錯誤肯适,而應(yīng)引發(fā)異常变秦,或者在某些情況下,只需記錄錯誤而不引發(fā)異常即可框舔。
例如蹦玫,NSDictionary
的objectForKey:
方法要么返回找到對象,要么找不到時返回nil雨饺。NSArray
的objectAtIndex:
方法則永不能返回nil钳垮,因為NSArray
對象不能存儲nil并且根據(jù)定義,任何越界訪問都是編程錯誤额港,應(yīng)導(dǎo)致異常饺窿。許多init
方法當(dāng)他們根據(jù)提供的參數(shù)無法初始化時也會返回nil。
在少數(shù)情況下移斩,一個方法確實需要多個不同的錯誤代碼肚医,這種情況則應(yīng)在按引用參數(shù)中指定他們,該參數(shù)返回錯誤代碼向瓷、本地化錯誤字符串或一些其他的描述錯誤的信息肠套。
框架數(shù)據(jù)
? 處理框架數(shù)據(jù)的方式會影響性能、跨平臺穩(wěn)定性和其他一些目的猖任。本節(jié)討論涉及框架數(shù)據(jù)的技術(shù)你稚。
常量數(shù)據(jù)(Constant Data)
? 從性能方面考慮,最好將盡可能多的框架里面的數(shù)據(jù)標(biāo)記為常量朱躺,這樣能減少Mach-O二進(jìn)制文件的__DATA
段的大小刁赖。不是const
修飾的全局和靜態(tài)數(shù)據(jù)最終都會出現(xiàn)在__DATA
段的__DATA
節(jié)中。這種數(shù)據(jù)會占用每一個使用到該框架的應(yīng)用中的部分內(nèi)存长搀。盡管額外的500字節(jié)(例如)看起來無足輕重宇弛,但可能會導(dǎo)致所需的頁數(shù)增加-每個應(yīng)用額外增加4KB。
? 你應(yīng)該將任何常量數(shù)據(jù)用const
來修飾源请。如果block中沒有char *
指針枪芒,那么就會導(dǎo)致數(shù)據(jù)被放在__TEXT
段(這會使這段代碼真正地保持不變)彻况;否則這段代碼就會被放在__DATA
段中,但不會被寫入(除非未執(zhí)行預(yù)綁定或者因為必須在加載時滑動二進(jìn)制文件而違反了預(yù)綁定)
? 你應(yīng)該初始化靜態(tài)變量去確保他們被合并到__DATA
段的__DATA
節(jié)中舅踪,而不是__bss
節(jié)中纽甘。如果沒有明顯的值可用于初始化,請使用0硫朦、NULL贷腕、0.0或者適當(dāng)?shù)闹怠?/p>
位域(Bitfields)
? 如果代碼假定該值是布爾值的話,使用帶符號的值作為位域(尤其是一位位域)則往往會導(dǎo)致奇奇怪怪的行為咬展。因為只能在這樣的位域中存儲的值是0和-1(取決于編譯器的實現(xiàn))泽裳,所以講該位域與1進(jìn)行比較是錯誤的。例如破婆,如果你在代碼里遇到這種情況:
BOOL isAttachment:1;
int startTracking:1;
? 你應(yīng)該把類型改為unsigned int
涮总。
? 位域的另一個問題是歸檔。通常不應(yīng)該以他的表現(xiàn)形式去寫入磁盤或歸檔祷舀,因為在另一種架構(gòu)或者編譯器上面再次讀取的時候格式可能會不一樣瀑梗。
內(nèi)存分配(Memory Allocation)
? 在寫框架的時候,盡量避免完全分配內(nèi)存裳扯。如果處于某種原因需要臨時緩沖區(qū)抛丽,通常使用堆棧要比分配緩沖區(qū)更好。但是堆棧的大小有限制(通呈尾颍總共大小為512KB)亿鲜,因此是否使用堆棧取決于功能和所需緩沖區(qū)的大小。通常冤吨,如果緩沖區(qū)的大小小于等于1000字節(jié)蒿柳,則可以使用堆棧。
? 一種策略是一開始使用堆棧漩蟆,如果大小增長到超出了堆棧緩沖區(qū)的大小垒探,則切換成分配到內(nèi)存的緩沖區(qū)。以下代碼就是做的這件事:
#define STACKBUFSIZE (1000 / sizeof(YourElementType))
YourElementType stackBuffer[STACKBUFSIZE];
YourElementType *buf = stackBuffer;
int capacity = STACKBUFSIZE; // In terms of YourElementType
int numElements = 0; // In terms of YourElementType
while (1) {
if (numElements > capacity) { // Need more room
int newCapacity = capacity * 2; // Or whatever your growth algorithm is
if (buf == stackBuffer) { // Previously using stack; switch to allocated memory
buf = malloc(newCapacity * sizeof(YourElementType));
memmove(buf, stackBuffer, capacity * sizeof(YourElementType));
} else { // Was already using malloc; simply realloc
buf = realloc(buf, newCapacity * sizeof(YourElementType));
}
capacity = newCapacity;
}
// ... use buf; increment numElements ...
}
// ...
if (buf != stackBuffer) free(buf);
對象比較(Object Comparison)
? 值得注意的是怠李,通用的對象比較方法-isEqual:
和與對象類型關(guān)聯(lián)的比較方法如-isEqualToString:
有著很大的區(qū)別圾叼。-isEqual:
方法允許你講任意對象作為參數(shù)傳遞,如果對象不是同一類捺癞,則返回NO夷蚊。而-isEqualToString:
和-isEqualToArray:
之類的方法通常假定參數(shù)為指定的類型(即接收方的類型)。因此翘簇,他們內(nèi)部不會進(jìn)行類型檢查撬码,因此他們執(zhí)行速度更快儿倒、但并不那么安全版保。對于外部資源呜笑,例如應(yīng)用程序的info.plist
或首選項Preferences
,首選使用-isEqual:
方法彻犁,因為他更安全叫胁;當(dāng)已知類型時,請使用-isEqualToString:
汞幢。
? 關(guān)于-isEqual:
方法的另一點是他與hash方法的關(guān)系驼鹅。對于Cocoa集合中的對象(例如NSDictionary
或者NSSet
),一個基本不變式是如果[A isEqual:B] == YES
森篷,那么[A hash] == [B hash]
输钩。因此,如果在類中重寫了-isEqual:
方法仲智,則還需要重寫-hash
方法以保證該不變式买乃。默認(rèn)情況下,-isEqual:
方法查找每個對象地址的指針相等钓辆,并且hash根據(jù)每個對象的地址返回一個哈希值剪验,因此該不變量成立。