開發(fā)cocoa框架、插件或者其它帶公共API的可執(zhí)行文件需要不同于應(yīng)用開發(fā)一些方法和慣例。你的產(chǎn)品的主要客戶是開發(fā)人員,這些人員不會(huì)對(duì)你的接口迷惑是很重要的。這時(shí)候,API的命名規(guī)則就將派上用場,它可以幫助你讓你的接口清晰、一致。有些編程技術(shù)對(duì)framework來說是特殊或者極其重要的,比如版本、兼容性、錯(cuò)誤處理和內(nèi)存管理。這個(gè)話題包含了Cocoa的命名規(guī)則和framework的編程練習(xí)建議。
文檔結(jié)構(gòu)
本文的主題包含兩種基本類型。第一種也是最大的一組是編程接口的命名規(guī)則。這是與Apple應(yīng)用再它自己的Cocoa框架上相同的規(guī)則。(除了少數(shù)例外)這些命名規(guī)則的文章包含一下內(nèi)容:
- 代碼命名基礎(chǔ)
- 方法命名
- 函數(shù)命名
- 屬性命名和數(shù)據(jù)類型
- 可接受的縮寫和縮略詞
第二種討論framework編程的方法(目前成員之一):
- 給framework開發(fā)者的一些技巧與技術(shù)
代碼命名基礎(chǔ)
面向?qū)ο蟮能浖斓脑O(shè)計(jì)一個(gè)經(jīng)常被忽視的方面是類、方法、函數(shù)、常量和編程接口的其他元素的命名。這個(gè)部分討論一些對(duì)多數(shù)Cocoa接口通用的命名規(guī)則。
一般原則
清晰
- 盡可能清晰、簡潔,但不能因?yàn)楹喍潭鵂奚逦奶匦?
| 代碼 | 評(píng)價(jià) |
|---|---|
| insertObject:atIndex: | Good |
| insert:at: | 不清晰,插入的是什么?at表示什么 |
| removeObjectAtIndex: | Good |
| removeObject: | Good,它移除參數(shù)中提到的對(duì)象 |
| remove: | 不清晰,什么將要被移除? |
- 一般來說,不要使用縮寫名稱。拼寫完全,即時(shí)他們很長
| 代碼 | 評(píng)價(jià) |
|---|---|
| destinationSelection | Good |
| destSel | 不清晰 |
| setBackgroundColor: | Good |
| setBkgColor: | 不清晰 |
你可能認(rèn)為某個(gè)縮寫是大家都知道的,但是它可能不是。特別是遇到有不同文化或語言背景的開發(fā)者調(diào)用你的方法或者函數(shù)。
- 但是,某些縮寫在已經(jīng)在很長一段歷史里被廣泛使用了。那么你可以繼續(xù)使用它們,你可以查看** 被接受的縮寫和縮略詞**
- 避免在API名字里使用有歧義的單詞,比如可以以超過1種方式解讀的方法名
| 代碼 | 評(píng)價(jià) |
|---|---|
| sendPort | 它表示發(fā)送一個(gè)接口還是返回? |
| displayName | 它是顯示一個(gè)名字還是在用戶界面里返回接收者的標(biāo)題? |
一致性
- 盡量在Cocoa編程接口里自始至終使用一致的命名。如果你不確定,瀏覽當(dāng)前頭文件或者先例的參考文檔。
- 當(dāng)你有一個(gè)使用了多態(tài)方法的類的時(shí)候,一致性是尤其重要的。在不同類做了相同事情的方法應(yīng)該有相同的名字。
| 代碼 | 評(píng)價(jià) |
|---|---|
| - (NSInteger)tag | 在NSView,NSCell,NSControl中定義過 |
| - (void)setStringValue:(NSString *) | 在很多Cocoa類里定義過 |
你也可以查看方法參數(shù)
無自身參照
- 名稱不應(yīng)該自我涉及
| 代碼 | 評(píng)價(jià) |
|---|---|
| NSString | Okay. |
| NSStringObject | 自我涉及 |
- 對(duì)這個(gè)規(guī)則來說被隱藏的常量是個(gè)例外。(因此可以按位運(yùn)算結(jié)合)例如通知名的常量。
| 代碼 | 評(píng)價(jià) |
|---|---|
| NSUnderlineByWordMask | Okay. |
| NSTableViewCalumnDidMoveNotification | Okay |
前綴
在編程接口名中,前綴是非常重要的。它們可以區(qū)分軟件的不通功能區(qū)域。通過這個(gè)軟件被打包成一個(gè)framework或者與framework相近的東西。第三方開發(fā)者和蘋果定義的前綴標(biāo)識(shí)能夠防止沖突。(當(dāng)然也包括蘋果的framework之間的標(biāo)識(shí))
- 前綴有一個(gè)規(guī)定的格式。它由2、3個(gè)大寫字母組成,并且不使用下劃線或者子前綴。這是幾個(gè)例子
| 前綴 | Cococa Framework |
|---|---|
| NS | Foundation |
| NS | Application Kit |
| AB | Address Book |
| IB | Interface Builder |
在為類、協(xié)議、函數(shù)、常量和typedef結(jié)構(gòu)體中命名時(shí)使用前綴。不要在為方法命名時(shí)使用前綴;方法存在于定義他們的類的命名空間中。當(dāng)然,也不要在文件命名時(shí)使用前綴。
書寫規(guī)則
下面是一些命名API元素時(shí)的一些書寫規(guī)則
- 對(duì)多個(gè)單詞命名,不要使用標(biāo)點(diǎn)符號(hào)作為命名的一部分或者分離它們(下劃線,破折號(hào)等等);相反,利用單詞的首字母組合起來(例如:runTheWordsTogether)-這就是著名的駝峰規(guī)則。然而,還是要注意以下條件:
- 對(duì)于方法的命名,以小寫字母作為單詞的開頭。不要使用前綴。
fileExistsAtPath:isDirectory: - 對(duì)這個(gè)指南有一個(gè)方法命名的另外就是以廣為人知的首字母縮寫,例如,TIFFRepresentation(NSImage)。
NSRunAlertPanel NSCellDisabled - 避免使用下劃線作為前綴意義在于它是被用于私有的方法命名(可以用它做實(shí)例變量).蘋果保留其使用。第三方使用可能導(dǎo)致命名空間的沖突。它們可能無意間重寫一個(gè)它們擁有的私有方法。參考** 私有方法 **的來查看私有API的書寫規(guī)則建議。
類和協(xié)議命名
類名應(yīng)該包含一個(gè)對(duì)類的清晰的表達(dá)或者要做什么的名詞。命名應(yīng)該有一個(gè)恰當(dāng)?shù)那熬Y(參考前綴)。Foundation和application框架里充滿了例子;比如:NSString,NSDate,NSScanner,NSApplication,UIApplication,NSButton和UIButton。
協(xié)議(Protocols)應(yīng)當(dāng)根據(jù)它的分組行為來命名
- 大部分協(xié)議會(huì)把一些彼此相關(guān)但又不合類關(guān)聯(lián)的方法歸結(jié)在一起,形成一個(gè)特殊的方法集合。這種類型的協(xié)議命名應(yīng)當(dāng)不與其類名混淆。一個(gè)通用習(xí)慣是使用動(dòng)名詞("...ing")的格式:
| 代碼 | 評(píng)價(jià) |
|---|---|
| NSLocking | Good |
| NSLock | Poor(像一個(gè)類的名字) |
- 有些協(xié)議組成一些并不關(guān)聯(lián)的方法(而不是創(chuàng)建幾個(gè)分離的小協(xié)議)。這些協(xié)議經(jīng)常跟一個(gè)類聯(lián)系起來,主要通過這個(gè)類來表達(dá)協(xié)議。在這種情況下,習(xí)慣上讓協(xié)議的名字與類保持一致。
這種協(xié)議的一個(gè)例子是NSObject協(xié)議。你可以使用這種協(xié)議的方法來查詢在一個(gè)對(duì)象在類里的層次,去調(diào)用一個(gè)指定的方法,增加或者減少它的引用計(jì)數(shù)。由于NSObject類提供了這些方法的主要表現(xiàn),所以我們使用類來做協(xié)議的命名。
頭文件
如何給頭文件命名是非常重要的,因?yàn)橥ㄟ^合理的習(xí)慣,你用來指示這些文件包含的內(nèi)容:
- 聲明一個(gè)獨(dú)立的類或協(xié)議。如果一個(gè)類或代理是獨(dú)立的,請將其用它的類名或協(xié)議名命名的文件里獨(dú)立聲明。
| 頭文件 | 聲明 |
|---|---|
| NSLocale.h | NSLocale類 |
- 聲明相關(guān)的類跟協(xié)議。把相關(guān)聯(lián)的聲明(類、類目和協(xié)議)放到使用與同個(gè)文件里,并使用主要的類或類目或協(xié)議做名稱。
| 頭文件 | 聲明 |
|---|---|
| NSString.h | NSString和NSMutableString類 |
| NSLock.h | NSLocking協(xié)議和NSLock,NSConditionLock和NSRecursiveLock類 |
- 包含framework的頭文件。每個(gè)framework應(yīng)該有一個(gè)以其命名的頭文件,其中包含了所有這個(gè)framework的頭文件。
| 頭文件 | Framework |
|---|---|
| Foundation.h | Foundation.framework |
- 給其它的framework添加接口。如果你在某個(gè)framework里給別的framework
的類添加類別,添加給原始的類添加"Additions"作為頭文件的名稱;在Application Kit里的一個(gè)例子就是NSBundleAdditions.h頭文件。 - 聯(lián)系方法與數(shù)據(jù)類型。如果你有一個(gè)相關(guān)聯(lián)的方法、常數(shù)、結(jié)構(gòu)體和其他數(shù)據(jù)類型,把他們放到一個(gè)適當(dāng)名字的頭文件里,比如NSGraphics.h(Application Kit)。
方法命名
方法可能是你程序界面里最普遍的元素了,所以你應(yīng)該尤其注意如何給它們命名。這個(gè)部分我們方法方面的命名。
一般規(guī)則
這里有一些需要在方法命名里常用的指南需要記?。?/p>
以小寫字母開頭并且后面單詞的第一個(gè)字母要大寫。不要使用前綴。參照書寫規(guī)則。
對(duì)這個(gè)指南來說有兩個(gè)特例。你可能使用某些常用的大寫字母縮寫(比如TIFF或者PDF),你可能使用來標(biāo)識(shí)是有方法(詳細(xì)查看私有方法)。對(duì)于一個(gè)對(duì)象動(dòng)作的方法,使用動(dòng)詞來開頭:
- (void)invokeWithTarget:(id)target;
- (void)selectTabViewItem:(NSTabViewItem *)tabViewItem;
不要使用"do"或"dose"作為命名的一部分,因?yàn)檫@些輔助動(dòng)詞很少有添加的意義。同時(shí),也不要再動(dòng)詞前面使用副詞跟形容詞。
- 如果這個(gè)方法返回返回接收者的某個(gè)屬性,直接使用屬性命名。使用"get"是沒有必要的,除非間接返回一個(gè)或多個(gè)值。
| 代碼 | 評(píng)價(jià) |
|---|---|
| - (NSSize)cellSize; | 正確 |
| - (NSSize)calcCellSize; | 錯(cuò)誤 |
| - (NSSize)getCellSize; | 錯(cuò)誤 |
你也可以參考"訪問方法"
- 在所有參數(shù)前使用關(guān)鍵字。
| 代碼 | 評(píng)價(jià) |
|---|---|
| - (void)sendAction:(SEL)aSelector toObject:(id)anObject forAllCells:(BOOL)flag; | 正確 |
| - (void)sendAction:(SEL)aSelector :(id)anObjject :(BOOL)flag; | 錯(cuò)誤 |
- 在參數(shù)前使用詞匯修飾參數(shù)。
| 代碼 | 評(píng)價(jià) |
|---|---|
| - (id)viewWithTag:(NSInteger)aTag; | 正確 |
| - (id)taggedView:(int)aTag; | 錯(cuò)誤 |
- 當(dāng)你添加一個(gè)比繼承的方法更加詳細(xì)的時(shí)候,在現(xiàn)存方法之后添加新的關(guān)鍵字。
| 代碼 | 評(píng)價(jià) |
|---|---|
| - (id)initWithFrame:(CGRect)frameRect; | NSView,UIView |
| - (id)initWithFrame:(CGRect)frameRect mode:(int)aMode cellClass:(Class)factoryId numberOfRows:(int)rowsHigh numberOfColumns:(int)colsWide; | NSMatrix,NSView的子類 |
雖然"and"在這個(gè)例子里聽起來很不錯(cuò),但是當(dāng)你創(chuàng)建了越來越多的關(guān)鍵字的時(shí)候就會(huì)出現(xiàn)問題。
- 如果方法描述了兩個(gè)分離的事件,使用"and"來連接他們。
| 代碼 | 評(píng)價(jià) |
|---|---|
| - (BOOL)openFile:(NSString *)fullPath withApplication:(NSString *)appName andDeactivate:(BOOL)flag; | NSWorkspace |
訪問方法
"訪問方法"即是對(duì)象的屬性的設(shè)置、獲取方法。它們有推薦的格式,取決于其屬性如何表達(dá):
- 如果用名詞表述一個(gè)屬性,格式是:
- (type)noun:
- (void)setNoun:(type)aNoun;
例子:
- (NSString *)title;
- (void)setTitle:(NSString *)aTitle;
- 如果用形容詞表述一個(gè)屬性,格式是:
- (BOOL)isAdjective;
- (void)setAdjective:(BOOL)flag;
例子:
- (BOOL)isEditable;
- (void)setEditable:(BOOL)flag;
- 如果用動(dòng)詞描述一個(gè)屬性,格式是:
- (BOOL)verbObject;
- (void)setVerbObject:(BOOL)flag;
例子:
- (BOOL)showsAlpha;
- (void)setShowsAlpha:(BOOL)flag;
動(dòng)詞應(yīng)該使用現(xiàn)代時(shí)。
- 不要使用動(dòng)詞的分詞形式做動(dòng)名詞:
| 代碼 | 評(píng)價(jià) |
|---|---|
| - (void)setAcceptsGlyphInfo:(BOOL)flag; | 正確 |
| - (BOOL)acceptsGlyphInfo; | 正確 |
| - (void)setGlyphInfoAccepted:(BOOL)flag; | 錯(cuò)誤 |
| - (BOOL)glyphInfoAccepted; | 錯(cuò)誤 |
- 你可能使用情態(tài)動(dòng)詞(使用"can","should","will"等在動(dòng)詞前面修飾)來闡明意思,但是不要使用"do"或"dose"。
| 代碼 | 評(píng)價(jià) |
|---|---|
| - (void)setCanHide:(BOOL)flag; | 正確 |
| - (BOOL)canHide; | 正確 |
| - (void)setShouldCloseDocument:(BOOL)flat; | 正確 |
| - (BOOL)shouldCloseDocument; | 正確 |
| - (void)setDoesAcceptGlyphInfo:(BOOL)flag; | 錯(cuò)誤 |
| - (BOOL)doesAcceptGlyphInfo; | 錯(cuò)誤 |
只有在一個(gè)方法間接返回對(duì)象或值的時(shí)候時(shí)候才使用"get"。當(dāng)一個(gè)方法需要返回多個(gè)參數(shù)的時(shí)候,應(yīng)該使用一下格式:
| 代碼 | 評(píng)價(jià) |
|---|---|
| - (void)getLineDash:(float *)pattern count:(int *)count phase:(float *)phase; | 正確 |
在如下這些方法里,對(duì)于這些輸入輸出參數(shù),實(shí)現(xiàn)里面應(yīng)該可以接受NULL來表示調(diào)用者并不不要一個(gè)或多個(gè)返回值。
代理方法
代理方法是單一個(gè)事件發(fā)生的時(shí)候,某個(gè)對(duì)象在它的代理里調(diào)用的方法。他們有特有的格式,同樣也是用于一個(gè)對(duì)象的數(shù)據(jù)源調(diào)用:
- 是用發(fā)送消息的類的對(duì)象為方法的起始:
- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row;
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename;
類名省略前綴并且首字母小寫。
- 除非方法只有"調(diào)用者"一個(gè)參數(shù),否者類名需要附加一個(gè)冒號(hào)。
- (BOOL)applicationOpenUntitleFile:(NSApplication *)sender;
- 這里有一個(gè)特例就是,當(dāng)這個(gè)方法被左右通知發(fā)送的方法被調(diào)用的時(shí)候。這這種情況下,這個(gè)唯一的參數(shù)就是通知對(duì)象。
- (void)windowDidChangeScreen:(NSNotification *)notification;
- 雖然你可以使用"did"或者"will"給那些調(diào)用以讓代理去執(zhí)行某些其他對(duì)象的行為,但是使用"should"會(huì)更好。
- (BOOL)windowShouldClose:(id)sender;
集合方法
對(duì)于對(duì)象管理一組對(duì)象有如下格式:
- (void)addElement:(elementType)anObj;
- (void)removeElement:(elementType)anObj;
- (NSArray *)elements;
例子:
- (void)addLayoutManager:(NSLayoutManager *)obj;
- (void)removeLayoutManager:(NSLayoutManager *)obj;
- (NSArray *)layoutManagers;
以下是一些限制跟規(guī)定:
- 如果集合是無須的,返回一個(gè)NSSet對(duì)象而不是NSArray對(duì)象。
- 如果給集合插入的位置是很重要的,使用與下列方法類似的方法代替,或者添加到上面的方法里:
- (void)insertLayoutManager:(NSLayoutManager *)obj atIndex:(int)index;
- (void)removeLayoutManagerAtIndex:(int)index;
集合方法有很多實(shí)現(xiàn)細(xì)節(jié)需要記住:
- 這些方法通常需要持有插入對(duì)象的所有權(quán),所以添加或插入他們的代碼比如retain它們,并且移除他們的代碼也必須release它們。
- 如果插入對(duì)象需要有 一個(gè)指向主對(duì)象的指針,你一般會(huì)使用一個(gè)set...方法來設(shè)置一個(gè)指針,但是不要retain。在
insertLayoutManager:atIndex:方法里,** NSLayoutManager ** 類使用如下方法:
- (void)setTextStorage:(NSTextStorage *)textStorage;
- (NSTextStorage *)textStorage;
你通常不會(huì)直接調(diào)用setTextStorage:方法,但是可能會(huì)想重寫它。
上面集合方法的另一個(gè)通用例子來自NSWindow類:
- (void)addChildWindow:(NSWindow *)childwin ordered:(NSWindowOrderingMode)place;
- (void)removeChildWindow:(NSWindow *)childWin;
- (NSArray *)childWindows;
- (NSWindow *)parentWindow;
- (void)setParentWindow:(NSWindow *)window;
方法參數(shù)
對(duì)于方法參數(shù)的命名有一些通用規(guī)則:
- 跟隨方法的參數(shù),以些小字母開頭,在它之后的單詞首字母要大寫。(比如:
removeObject:(id)anObject)。 - 不用再命名里使用"pointer"或者"ptr"。使用參數(shù)的類型而不是它是不是一個(gè)指針。
- 避免使用"one-"和"tow-letter"這種形式為參數(shù)命名。
- 避免使用縮寫導(dǎo)致只有幾個(gè)字母。
一般來說,一下關(guān)鍵字和參數(shù)經(jīng)常一起使用:
...action:(SEL)aSelector
...alignment:(int)mode
...atIndex:(int)index
...content:(NSRect)aRect
...doubleValue:(double)aDouble
...floatValue:(float)aFloat
...font:(NSFont *)fontObj
...frame:(NSRect)frameRect
...intValue:(int)anInt
...keyEquivalent:(NSString *)charCode
...length:(int)numBytes
...point:(NSPoint)aPoint
...stringValue:(NSString *)aString
...tag:(int)anInt
...target:(id)anObject
...title:(NSString *)aString
私有方法
大多數(shù)情況下,私有方法的命名與工友方法的命名是一樣的。但是一個(gè)通用的做法是給私有方法一個(gè)前綴,使得能夠更簡單的把它與公有方法區(qū)別開來。即使在這種情況下,這種給私有方法命名的方式也會(huì)導(dǎo)致奇怪的問題。當(dāng)你設(shè)計(jì)一個(gè)Cocoa框架類下的子類的時(shí)候,你不知道你的私有方法是不是無意中重寫了框架里的私有方法。
Cocoa框架中的大多數(shù)私有方法都有一個(gè)下劃線的前綴(比如:_fooData)來標(biāo)記他們是私有方法?;谶@個(gè)事實(shí)有兩條建議。
- 不要使用下劃線來左右你私有方法的前綴。蘋果公司保留這個(gè)規(guī)范。
- 如果你子類化一個(gè)大的Cocoa框架的一個(gè)子類(比如NSView或者UIView),并且你確定你的私有方法的命名與父類不同,你可以給你的私有方法添加不同的前綴。前綴應(yīng)該竟可能唯一,可能是一個(gè)基于你公司或項(xiàng)目的類似有"XX_"的格式。比如工程名"Byte Flogger",前綴可以是
BF_addObject:
雖然給私有方法一個(gè)前綴的命名可能與早前在為類命名的規(guī)則相矛盾,這這里的意圖是不一樣的:防止無意間重寫父類的私有方法。
函數(shù)命名
Objective-C允許你使用函數(shù)跟方法來表達(dá)行為。當(dāng)潛在的對(duì)象總是一個(gè)單例或者當(dāng)你解決一個(gè)明顯的函數(shù)式子系統(tǒng)的時(shí)候,你應(yīng)該使用函數(shù)而不是說類方法。
函數(shù)命名有如下一些規(guī)范你需要遵守;
- 函數(shù)命名的格式與方法命名差不多,但是有一些例外:
- 在類跟常數(shù)的使用上,它們以同樣的前綴開頭。
- 前綴后的第一個(gè)單詞是大寫。
- 大多數(shù)以動(dòng)詞開頭的函數(shù)描述了該函數(shù)的行為:
NSHighlightRect
NSDeallocateObject
查詢屬性的函數(shù)有額外的命名規(guī)則:
- 如果函數(shù)返回它第一個(gè)參數(shù)的屬性,省略動(dòng)詞。
unsigned int NSEventMaskFromType(NSEventType type)
float NSHeight(NSRect aRect)
- 如果通過引用返回值,使用"Get"。
const char *NSGetSizeAndAlignment(const char *typePtr, unsigned int *sizep,unsigned int *alignp)
- 如果返回值是一個(gè)Boolean,函數(shù)應(yīng)該以一個(gè)判斷動(dòng)詞開頭。
BOOL NSDecimalIsNotANNumber(const NSDecimal *decimal)
屬性與數(shù)據(jù)類型的命名
這個(gè)部分表述了聲明屬性,實(shí)例變量,常數(shù),通知等的命名規(guī)范。
屬性聲明和實(shí)例變量
屬性聲明影響一個(gè)屬性的訪問方法,所以其規(guī)范與訪問方法的命名大致相同。如果屬性是以名詞或動(dòng)詞表達(dá),格式是:
@property(...)名詞或動(dòng)詞類型;
例子:
@property(strong) NSString *title;
@property(assign) BOOL showsAlpha
如果屬性聲明的詞匯是形容詞,屬性名省略"is"前綴,但是規(guī)范上需要給get方法加上"is",例如:
@property(assign, getter=isEditable)BOOL editable;
在大多數(shù)情況下,當(dāng)你聲明了一個(gè)屬性的時(shí)候,你同時(shí)也生成了一個(gè)實(shí)例變量。
確保簡明扼要的表述了屬性的存儲(chǔ)。一般來說,你不應(yīng)該直接訪問實(shí)例變量;正確的做法是你應(yīng)該使用訪問方法(你在init和dealloc方法里直接訪問實(shí)例變量)。為了標(biāo)記這個(gè),使用"-"前綴來標(biāo)記實(shí)例變量,比如:
@implementation MyClass{
BOOL _showsTitle;
}
如果你使用屬性聲明來合成實(shí)例變量,在@synthesize中指定實(shí)例變量的名字。
@implementation MyClass
@synthesize showsTitle = _showsTitle;
在把一個(gè)實(shí)例變量添加到一個(gè)類的時(shí)候,有一些需要考慮的東西要記住:
- 避免直接聲明公共實(shí)例變量。
開發(fā)者應(yīng)該把他們考慮成一個(gè)對(duì)象的接口,而不是它數(shù)據(jù)的組合細(xì)節(jié)。你可以使用屬性聲明和合成相應(yīng)的實(shí)例變量來避免直接聲明實(shí)例變量。 - 如果你需要聲明一個(gè)實(shí)例變量,可以使用
@provate或者@protected來聲明它.
如果你希望你的類會(huì)被子類化,并且子類將需要直接訪問數(shù)據(jù),可以使用@protected來修飾。
如果一個(gè)實(shí)例變量可被類的實(shí)例訪問,確保你為它寫了訪問方法。(可能的話,盡量使用屬性聲明)。
常數(shù)
常數(shù)的命名規(guī)則根據(jù)它創(chuàng)建的方式而不同。
枚舉常量
- 使用枚舉來關(guān)聯(lián)一組有聯(lián)系常量整數(shù)。
- 枚舉常量與
typedef遵守函數(shù)的命名規(guī)范。以下例子來做NSMatrix.h:
typedef enum _NSMatrixMode{
NSRadioModeMatrix = 0,
NSHighlightModeMatrix = 1,
NSListModeMatrix = 2,
NSTrackModeMatrix =3,
} NSMatrixMode;
注意typedef標(biāo)示(_NSMatrixMode在上面的例子里)是沒有必要的。
- 你可以創(chuàng)建不具名的枚舉,比如位掩碼,比如:
enum {
NSBorderlessWindowMask = 0,
NSTitledWindowMask = 1 << 0,
NSClosableWindowMask = 1 << 1,
NSMiniaturizableWindowMask = 1 << 2,
NSResizableWindowMask = 1 << 3
};
使用const創(chuàng)建的常量
- 使用
const來為浮點(diǎn)型創(chuàng)建常量。如果這個(gè)常量不與其他常量關(guān)聯(lián),你可以使用const來創(chuàng)建一個(gè)interger類型的常量;其他情況,可以使用枚舉。 -
const類型的常量所遵循格式的一個(gè)例子:
const float NSLightGray
對(duì)于枚舉常量,其命名規(guī)范與函數(shù)的命名一樣。
其它類型的常量
- 一般情況下,不要使用
#define的預(yù)處理命令來創(chuàng)建常量。對(duì)于interger類型的常量,使用枚舉,對(duì)于浮點(diǎn)型指針使用const,如上邊所述。 - 使用大寫字母定義預(yù)處理宏,來確定一塊代碼是否會(huì)被處理。例如:
#ifdef DEBUG
- 需要注意的是,編譯器的宏定義頭尾都有雙下劃線。例如:
__MACH__
- 字符串類型的常量通常被用作通知名和字典的key。通過使用字符串常量,你能確保編譯器識(shí)別的值是正確的(就是說,它有拼寫檢查)。cocoa框架提供了很多字符串常量的常數(shù)。例如:
APPKIT_EXTERN NSString * NSPrintCopies;
字符串常量的值將會(huì)在實(shí)現(xiàn)文件里分配。(注意appkit_extern宏定義在Objective-C中等價(jià)于extern)
通知和異常
通知與異常的命名遵循相似的規(guī)則。但是他們有自己的被建議的使用模式。
通知
如果一個(gè)類有代理,它的多數(shù)通知將會(huì)通過定義一個(gè)代理方法來讓代理接受。這些通知的名字應(yīng)該能夠反映相應(yīng)的代理方法。例如,由于全局的NSApplication對(duì)象注冊了一個(gè)applicationDidBecomeActive:方法,所以不論何時(shí)當(dāng)程序發(fā)出NSApplicationDidBecomeActiveNotification通知,它都能收到消息。
通知由如下形式的全局NSString對(duì)象標(biāo)示:
[Name of associated class] + [Did | will] +[UniquePartOfName] + Notification
例如:
NSApplicationDidBecomeActiveNotification
NSWindowDidMiniaturizeNotification
NSTextViewDidChangeSelectionNotification
NSColorPanelColorDidChangeNotification
異常
雖然你可以自由的使用異常(就是說,由NSException類和其相關(guān)函數(shù)提供的機(jī)制)來為你選擇的如何目的,Cocoa保留編碼錯(cuò)誤的異常。比如數(shù)組越界。Cocoa沒有使用異常來解決常規(guī)的,可預(yù)料的錯(cuò)誤條件。在這些情況下,使用nil,NULL,NO或者錯(cuò)誤代碼來做返回值。更多細(xì)節(jié),請查看錯(cuò)誤處理指南。
異常由如下形式的全局NSString對(duì)象表示:
[Prefix] + [UniquePartOfName] + [Exception]
名稱的唯一部分應(yīng)當(dāng)由應(yīng)當(dāng)由幾個(gè)單詞組成,并且首字母大寫。這是幾個(gè)例子:
NSColorListIOException
NSColorListNotEditableException
NSDraggingException
NSFontUnavailableException
NSIllegalSelectorException
可接受的縮寫和首字母縮略詞
一般來說,當(dāng)你設(shè)計(jì)編碼接口的時(shí)候,你不應(yīng)該使用縮寫名。然而以下的縮寫列表在過去被使用,你現(xiàn)在仍然可以繼續(xù)使用它們。使用縮寫的時(shí)候有一些事你必須要知道:
- 過去C庫有一些已經(jīng)用了很久的縮寫,比如
alloc、getc是被允許的。 - 你可能在參數(shù)名上更多的使用縮寫(比如,"obj","imageRep")。
| 縮寫 | 本來的單詞 |
|---|---|
| alloc | Allocate |
| alt | Alternate |
| app | Application |
| calc | Calculate |
| dealloc | Deallocate |
| func | Function |
| horiz | Horizontal |
| info | Information |
| init | initialize |
| int | integer |
| max | Maximum |
| min | Minimum |
| msg | Message |
| nib | Interface Builder archive |
| pboard | Pasteboard |
| rect | Rectangle |
| Rep | Representation |
| temp | Temporary |
| vert | Vertical |
你可能在電腦工程里使用縮寫和首字母縮略詞取代他們。這里有一些廣為人知的首字母縮寫:
ASCII
PDF
XML
HTML
URL
RTF
HTTP
TIFF
JPG
PNG
GIF
LZW
ROM
RGB
CMYK
MIDI
FTP
給框架開發(fā)者的一些小技巧跟技術(shù)
框架開發(fā)者在編寫代碼的時(shí)候必須必其他開發(fā)者更加注意。其他的客戶端應(yīng)用可以連接到他們的框架,正因如此,框架里的任何不足都可能通過一個(gè)系統(tǒng)方法。你可以采用以下討論到的編程技術(shù)來保證你框架的效率跟完整性。
注意:這里面的有些技術(shù)不僅限于框架。你可以把它們運(yùn)用于應(yīng)用的開發(fā)當(dāng)中
初始化
以下建議包含框架的初始化。
類的初始化
initialize類方法給你一個(gè)只執(zhí)行一次代碼的地方。它是懶加載的,在其它類的方法之前調(diào)用。它一般被用用類的版本號(hào)設(shè)置。
runtime會(huì)給繼承鏈的每個(gè)類發(fā)送initialize方法。即使它并沒有實(shí)現(xiàn)它。因此它可能不止一次的調(diào)用類的initialize方法。(比如,一個(gè)子類并沒有實(shí)現(xiàn)它)。一般來說,你想要初始化代碼只執(zhí)行一次。有一種方法來保證這個(gè)過程就是使用dispatch_once():
+ (void)initialize{
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
//初始化代碼
}
}
注意:由于runtime給每個(gè)類發(fā)送初始化方法,如果子類沒有實(shí)現(xiàn)initialize方法,initialize可能會(huì)在子類的上下文里被調(diào)用。那么這個(gè)調(diào)用就會(huì)讓父類來調(diào)用。如果你確實(shí)想在相關(guān)類的上下文里初始化,你可以使用如下檢查,而不是使用dispatch_once():
if (self == [NSFoo class]){
// the initializing code
}
你不應(yīng)該手動(dòng)調(diào)用initialize方法。如果你需要觸發(fā)初始化,調(diào)用一些無害的方法,比如:
[NSImage self];
指定初始化
指定初始化是一個(gè)類的init方法調(diào)用父類的init方法。(其他初始化者調(diào)用這個(gè)類的init方法)每個(gè)公共類都應(yīng)該有至少一個(gè)指定初始化方法。指定初始化的一個(gè)例子是NSView的 initWithFrame和NSResponder的init方法。這并不意味著init方法需要被重寫,有一個(gè)例子就是NSString和其它類簇的抽象類,子類被期望于實(shí)現(xiàn)它自己。
指定初始化方法應(yīng)該很容易被識(shí)別,因?yàn)樗男畔?duì)于想要子類化你的類的人來說是非常重要的。子類可以只重寫指定初始化方法給你并且其它的初始化將會(huì)像設(shè)計(jì)一樣的執(zhí)行。
當(dāng)你實(shí)現(xiàn)一個(gè)框架的類,你經(jīng)常會(huì)去實(shí)現(xiàn)像是initWithCoder:和encodeWithCoder:這類歸檔方法。注意,當(dāng)對(duì)象解檔沒有完成的時(shí)候 ,不要在初始化代碼路徑下做其他事情。如果你的類實(shí)現(xiàn)了歸檔,那么,從你的指定初始化方法和initWithCoder:(它本身就是指定初始化方法)來實(shí)現(xiàn)將是一個(gè)不錯(cuò)的方式。
初始化過程中的錯(cuò)誤檢查
一個(gè)好的初始化方法設(shè)計(jì)應(yīng)該完成以下這些步驟來保證恰當(dāng)?shù)腻e(cuò)誤檢查和錯(cuò)誤誤差:
- 通過調(diào)用父類的指定初始化方法來再分配自己。(就是類似于調(diào)用
self = [super init]) - 返回值為
nil的檢查,其指示了父類初始化期間發(fā)生了一些錯(cuò)誤。 - 初始化當(dāng)前類的時(shí)候如果出現(xiàn)了錯(cuò)誤,release當(dāng)前對(duì)象,并且返回
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)你給你的框架添加一個(gè)新的類或者方法,給每個(gè)新特性組指定新版本號(hào)是沒有必要的。開發(fā)者通常調(diào)用0bjective-C的runtime來檢查。比如使用respondsToselector:方法來判定在指定版本下某個(gè)特性是否有效。這些runtime測試是首先且是最動(dòng)態(tài)的方式來檢查新特性。
當(dāng)然,你可以雇傭一些技術(shù)人員來保證你框架的每個(gè)新特性被恰當(dāng)?shù)臉?biāo)記,并且盡可能與早期版本有相同的兼容性。
框架版本
當(dāng)現(xiàn)存的新特性或bug修復(fù)不能簡單的時(shí)候runtime檢測出來,你應(yīng)該為開發(fā)者提供一些檢查改變的方法。其中一個(gè)方法就是存儲(chǔ)一個(gè)當(dāng)前框架的額外版本號(hào),并使開發(fā)者能夠拿到這個(gè)版本號(hào)。
- 文檔根據(jù)版本號(hào)改變。
- 給你的框架設(shè)置一個(gè)版本,并且提供一個(gè)全局都能取到它的方法。你可能在你框架的詳情屬性列表(info.plist)存一個(gè)版本號(hào),然后就可以通過它拿到了。
鍵入存檔
如果你的框架需要被寫入到nib文件中,它們必須可以歸檔它們自己。你也需要?dú)w檔機(jī)制把所有文檔歸檔來保存文檔數(shù)據(jù)。
關(guān)于歸檔,你應(yīng)該考慮以下問題:
- 如果存檔中沒有某個(gè)key,調(diào)用它的值將會(huì)返回
nil,NULL,NO,0或者0.0,取決于被調(diào)用者的類型。對(duì)返回值做測試以便減少你寫出的數(shù)據(jù)。此外,你可以找到一個(gè)key是否被寫入存檔中。 - 歸檔與解檔方法都可以做一些事情阿里保證后面的兼容性。例如,新版本類的歸檔方法可能使用一個(gè)key寫入一個(gè)新值,但是仍然可以寫入舊文件以便舊版本的類仍然可以識(shí)別對(duì)象。此外,解檔方法可能想要通過一些合理的方法保留一些沒有的值,以便將來的版本能夠保持靈活性。
- 給框架的類的存檔key的命名規(guī)范建議是:以被用于框架里其他API元素的縮寫開頭,然后使用實(shí)例便利的名字。只要保證名字不與父類或子類沖突即可。
- 如果你有一個(gè)函數(shù)用來編寫基本的數(shù)據(jù)類型,確保使用唯一的值。例如,一個(gè)“archiveRect”通常存檔一個(gè)長方形,應(yīng)當(dāng)帶一個(gè)key參數(shù),或者使用它?;蛘撸绻鼘懭攵鄠€(gè)值(比如,四個(gè)floats),它應(yīng)當(dāng)給提供的key添加它自己唯一的比特。
- 由于編譯器和字節(jié)序的依賴,歸檔位字段可能是危險(xiǎn)的。只有當(dāng)遇到性能問題,許多bits需要被寫入很多次的時(shí)候才能歸檔它們??梢圆榭次蛔侄蔚慕ㄗh。
異常和錯(cuò)誤
大多數(shù)的cocoa框架并不強(qiáng)迫開發(fā)者去捕捉和解決異常。這是因?yàn)楫惓2⒉皇亲鳛檎?zhí)行的一部分而被創(chuàng)建。而且,它通常并不被用于傳遞預(yù)期的runtime或用戶錯(cuò)誤。這些錯(cuò)誤的例子包含:
- 找不到文件
- 找不到該用戶
- 在app中打開一個(gè)錯(cuò)誤格式的文件
- 字符串轉(zhuǎn)碼錯(cuò)誤
然而,cocoa創(chuàng)建異常來指示編碼錯(cuò)誤或者邏輯錯(cuò)誤,比如: - 數(shù)組越界
- 嘗試操作不可變的對(duì)象
- 錯(cuò)誤的參數(shù)類型
值得期望的是,開發(fā)者會(huì)在測試的時(shí)候捕獲到這些錯(cuò)誤,并在app發(fā)布前解決這些錯(cuò)誤。因此app不需要在運(yùn)行時(shí)解決這些異常。一個(gè)產(chǎn)生了一個(gè)異常,但是程序沒有捕捉到它,最高等級(jí)的默認(rèn)解決者一般會(huì)捕獲、報(bào)告、然后繼續(xù)執(zhí)行。開發(fā)者可以選擇替代默認(rèn)的異常。捕獲者給予更多錯(cuò)誤信息,并且提供保存數(shù)據(jù)和終止程序的選項(xiàng)。
錯(cuò)誤是cocoa框架與其他軟件庫不同的另一個(gè)地方。cocoa方法一般不返回錯(cuò)誤代碼。再有一個(gè)合理或簡單理由的錯(cuò)誤原因情況下,方法依靠簡單的布爾測試或者對(duì)象(nil/non-nil)返回值。記錄了NO或nil返回值的原因。您不應(yīng)該使用錯(cuò)誤代碼來指示要在運(yùn)行時(shí)處理的編程錯(cuò)誤,而是產(chǎn)生異常,或者在某些情況下僅僅記錄錯(cuò)誤而不引發(fā)異常。
比如,NSDictionary的objectForKey:方法會(huì)返回找到的對(duì)象,沒找到就會(huì)返回nil。NSArray的objectAtIndex:方法永遠(yuǎn)都不會(huì)返回nil,因?yàn)?code>NSArray對(duì)象不能存儲(chǔ)nil值,并且通過定義訪問越界的異常來指示編程錯(cuò)誤當(dāng)對(duì)象不能通過提供的參數(shù)正確的初始化的時(shí)候,許多init方法會(huì)返回nil。
在一些情況下,一些方法會(huì)有不同的錯(cuò)誤代碼。它應(yīng)該在一個(gè)引用參數(shù)中指定它們,該參數(shù)返回一個(gè)錯(cuò)誤代碼,一個(gè)本地化的錯(cuò)誤字符串,或一些描述錯(cuò)誤的其他信息.例如:你可能想要返回一個(gè)當(dāng)做NSError的對(duì)象,在Foundation中查看NSError.h頭文件的詳情。這個(gè)參數(shù)可能是更簡單的BOOL或者nil直接返回。該方法還應(yīng)遵守約定,所有參考引用參數(shù)是可選的,因此如果發(fā)送方不希望知道錯(cuò)誤,則允許發(fā)送方為錯(cuò)誤代碼參數(shù)傳遞NULL。
框架數(shù)據(jù)
你如何處理框架數(shù)據(jù)對(duì)性能,跨平臺(tái)兼容性和其他目的有影響。這個(gè)部分將討論包括框架數(shù)據(jù)的技術(shù)。
常量數(shù)據(jù)
由于性能的原因,盡可能的把框架數(shù)據(jù)標(biāo)記為使用常量,因?yàn)檫@樣做會(huì)減小Mach-O二進(jìn)制的__DATA段的大小。不是const的全局和靜態(tài)數(shù)據(jù)會(huì)在__DATA段的__DATA部分結(jié)束。這種數(shù)據(jù)在每個(gè)使用此框架的運(yùn)行實(shí)例中占用內(nèi)存。雖然多500字節(jié)的內(nèi)存占用可能沒關(guān)系,但是它可能會(huì)導(dǎo)致所需的頁面數(shù)量增加 - 每個(gè)應(yīng)用程序額外增加4千字節(jié)。
你應(yīng)該把任何常量數(shù)據(jù)標(biāo)記為const。如果在block里有char *,這將會(huì)導(dǎo)致數(shù)據(jù)降落在__TEXT字段。此外,它將會(huì)待在__DATA字段但是不睡寫入。(除非預(yù)加載沒有完成或者由于在加載時(shí)滑動(dòng)二進(jìn)制而被違反)。
你應(yīng)該初始化靜態(tài)變量來保證他們被合并入__DATA字段的__data部分而不是__bss部分。如果沒有明顯的值用于初始化,使用0,NULL,0.0或者任何恰當(dāng)?shù)闹怠?/p>
位字段
如果代碼假定值為布爾值,則對(duì)位字段使用有符號(hào)值,尤其是使用一位位域,可能會(huì)導(dǎo)致未定義的行為。 一位位域應(yīng)始終為無符號(hào)。 因?yàn)榭梢源鎯?chǔ)在這樣的位字段中的唯一值是0和-1(取決于編譯器實(shí)現(xiàn)),將該位域與1進(jìn)行比較是假的。 例如,如果你在代碼中遇到類似這樣的東西:
BOOL isAttachment:1;
BOOL startTracking:1;
你應(yīng)該把類型轉(zhuǎn)為無符號(hào)的int。
位字段的另一個(gè)問題是歸檔。 通常,您不應(yīng)以位格式將位字段寫入磁盤或歸檔,因?yàn)樵诹硪粋€(gè)架構(gòu)或另一個(gè)編譯器上再次讀取時(shí),格式可能不同。
內(nèi)存分配
在框架代碼中,最好的方法是避免分配內(nèi)存,如果你可以幫助。如果你因?yàn)橐恍├碛尚枰R時(shí)緩存,通常來說,使用堆棧比分配緩存更好。當(dāng)然,堆棧會(huì)限制大小(通常是512千比特),所以決定是否使用堆棧取決于功能和你需要的緩存大小。一般來說,如果你需要1000比特以下的緩存大小,使用堆棧是可接受的。
一個(gè)改進(jìn)是開始使用堆棧,但是如果大小要求超過堆棧緩沖區(qū)大小,則切換到malloc'ed緩沖區(qū)。 以下提供了一個(gè)代碼片段:
#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);
對(duì)象比較
你應(yīng)該意識(shí)到通用的對(duì)象比較方法isEqual:和與具體的對(duì)象類型聯(lián)系的比較方法(比如isEqualToString)的不同。isEqual方法運(yùn)行你通過任意參數(shù)所謂參數(shù),如果對(duì)象不是同一個(gè)類,則返回NO。isEqualToString:和isEqualToArray:方法經(jīng)常用于參數(shù)是確定的類型。因此他們沒有做類型檢查,所以他們很快,但是并不安全。從外部資源取回的值,比如從應(yīng)用的屬性列表(info.plist),使用isEqual:會(huì)更好,因?yàn)樗容^安全。當(dāng)類型是已知的,使用isEqualToString:來替換。
關(guān)于isEqual:的另一點(diǎn)是,它與hash方法相關(guān)聯(lián)。對(duì)于放置在基于散列的Cocoa集合(例如NSDictionary或NSSet)中的對(duì)象的一個(gè)基本不變量是,如果[A isEqual:B] == YES,則[A hash] == [B hash]。所以如果你在你的類中重寫isEqual:,你也應(yīng)該覆蓋hash來保留這個(gè)不變量。 默認(rèn)情況下isEqual:查找每個(gè)對(duì)象的地址的指針相等,并且hash基于每個(gè)對(duì)象的地址返回一個(gè)哈希值,所以這個(gè)不變量成立。
第一次翻譯 ==
問題應(yīng)該比較多,參考了一些別人的翻譯,同時(shí)還有谷歌的翻譯
不得不感嘆,谷歌的翻譯好牛逼。。。
發(fā)現(xiàn)錯(cuò)誤,歡迎指出