cocoa編碼指南(翻譯)

開發(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)該使用訪問方法(你在initdealloc方法里直接訪問實(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è)例子是NSViewinitWithFrameNSResponderinit方法。這并不意味著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ò)誤誤差:

  1. 通過調(diào)用父類的指定初始化方法來再分配自己。(就是類似于調(diào)用self = [super init])
  2. 返回值為nil的檢查,其指示了父類初始化期間發(fā)生了一些錯(cuò)誤。
  3. 初始化當(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)返回值。記錄了NOnil返回值的原因。您不應(yīng)該使用錯(cuò)誤代碼來指示要在運(yùn)行時(shí)處理的編程錯(cuò)誤,而是產(chǎn)生異常,或者在某些情況下僅僅記錄錯(cuò)誤而不引發(fā)異常。
    比如,NSDictionaryobjectForKey:方法會(huì)返回找到的對(duì)象,沒找到就會(huì)返回nilNSArrayobjectAtIndex:方法永遠(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ò)誤,歡迎指出

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容