Cocoa编码规范
介紹Cocoa編碼規范
開發一個Cocoa framework,插件或者其他具有公開API的可執行文件時,需要采取與那些應用開發不同的方法和約定。你產品的初始客戶端是開發者,非常重要的一點就是不要讓他們為你的編程接口感到困惑。以下便是API的命名約定,能夠幫助你讓你的接口保持一致和清晰,這些對于你來說,遲早排得上用場。同樣還有比較特殊的針對更重要的與框架有關的編程技術,例如,版本標注、二進制兼容性、錯誤處理和內存管理。此篇文章同樣包含Cocoa命名約定和推薦的框架編程實踐。
本文主要有兩部分。編程接口的命名規范以及關于框架編程方面的討論。
接口的命名規范
代碼命名基礎
對于面向對象軟件庫的設計,經常忽視的一點是類、方法、函數、常量以及其它編程接口元素的命名。本部分將會討論對于大多數的Cocoa接口的一些通用的命名約定。
總則
簡潔
-
盡可能簡單明了,但是過于簡單會讓人看不懂:
代碼評注 insetObject:atIndex 不錯 insert:at: 不明了; 插入了什么? “at”表示什么意思? removeObjectAtIndex: 不錯 removeObject: 不錯,因為這個方法表明了移除這個參數對象 remove: 不明了; 移除什么? -
一般來說,不要使用縮寫。使用全拼,即使單詞比較長
代碼評注 destinationSelection 不錯 destSel 不明了 setBackgroundColor: 不錯 setBkgdColor: 不明了
你可能認為有些縮寫必要有名,其實非你所料,尤其是當你的方法或者函數名遇到一個不同文化和語言的開發者時。比如在國內BAT,大家都知道說的是百度、阿里、騰訊,但是外國人卻不知道說的是什么。
-
然而,少數縮寫詞確實常見,而且有很長的使用歷史。你可以繼續使用它們;可以參見后文的可接受的縮略詞和首字母縮略詞。
-
避免模棱兩可的API命名,例如具有多種解釋的方法名。
代碼評注 sendPort 是要發送還是需要返回端口? displayName 是需要顯示一個名字還是需要返回一個UI的標題?
一致性
-
在整個Cocoa編程接口中的命名保持一致性。如果你不太確定,請查看當前的頭文件或者引用文檔。
-
當你一個類的方法需要利用到多態,這種情況下一致顯得尤為重要。方法需要在不同的類中具有相同的作用,并且需要有相同的名字。
代碼評注 - (NSInteger)tag 在UIView, UIControl中定義 - (void)setStringValue:(NSString *) 在許多Cocoa類中定義
無自引用
指不能在當前命名中附帶本身的屬性,比如一個人叫張三,我們不要把他叫做張三人、人張三。
-
名字不能自引用
代碼評注 NSString 沒問題 NSStringObject 自引用
代碼評注 NSUnderlineByWordMask 沒問題 NSTableViewColumnDidMoveNotification 沒問題
前綴
在編程接口命名中,前綴是一個重要部分。這個是用來區分軟件不同的功能范圍。這樣的不同的范圍,通常是一個框架中的包或者(例如基礎框架和應用包)相關的框架。前綴能夠防止第三方開發者定義的內容與Apple定義的內容相沖突。
-
前綴有規定的格式。它包含2-3個大寫字母,并且不會使用下劃線分割或者“子前綴”。下面有些例子:
前綴Cocoa框架 NS Foundation UI UIKit AB Address Book IB Interface Builder -
在類、協議、函數、常量和定義結構體命名的時候,請使用前綴。不要在方法命名的時候使用前綴;方法是有通過類來定義的命名空間。同樣,不要在結構體內的字段上使用前綴。
排版約定
在API元素命名的時候,請遵循一些簡單的排版約定:
-
對于由多個單詞組成的命名,不要使用標點符號作為名字的一部分,或者作為分隔符(下劃線、破折號等);而且每個單詞的首字母要大寫,并且連在一起(例如,runTheWordsTogether)——這個被稱為駝峰式命名法。然而,要注意以下限制:
- 對于方法名稱,首字母小寫,并將其他單詞的首字母大寫。不要使用前綴。例如:fileExistsAtPath:isDirectory:。對于方法的命名有一個例外的地方就是以一個有名的首字母縮寫詞打頭,例如NSImage的TIFFRepresentation方法。
- 對于函數和常量的命名,對于相關的類使用相同的前綴,并且緊接著后面的一個單詞首字母大寫。例如:NSRunAlertPanel、NSCellDisabled。
- 避免使用下劃線字符作為私有方法的命名前綴(對于使用下劃線字符作為實例變量的命名前綴是允許的)。Apple保留這種約定的使用權。如果第三方使用,可能會導致命名空間的沖突;可能會在毫不知情的情況下覆蓋了其中一個已經存在的私有方法,從而帶來災難性的后果。參見私有方法部分來獲取對于私有API方法命名的約定建議。
類和協議的命名
類的命名中應當包含一個能夠清楚的表明這個類(或者類的對象)是什么,或者是做什么的名詞。這個名字需要有一個適當的前綴。Foundation框架和應用框架里面到處都是例子;例如: NSString, NSDate, NSScanner, NSApplication, UIApplication, NSButton, 以及UIButton。
協議應該依據組作用來命名:
一般情況下,大多數協議組相關的方法與任何類都不相關。這種類型的協議應該在命名上與類區分開來。一個常見的約定就是使用動名詞(“…ing”)格式。例如:
| NSLocking | 不錯 |
| NSLock | 不是很好 (看起來像一個類的命名) |
-
一些協議中包含多個不相關的方法(而不是創建多個單獨的小的協議)。這些協議與類相關,這是協議最重要的表達方式。在這種情況下,我們約定,保持協議和類名相同的方式。
這種協議的例子就是NSObject協議。可以使用這個協議中的方法來查詢任意類層級的對象的位置,可以調用指定方法,增加或減少它的引用數。因為類NSObject提供了這些方法的初始表達式,協議的命名在類名之后。
頭文件
你如何命名頭文件非常重要,因為約定中,你用它來顯示這個文件包含的內容:
-
聲明一個單獨的類或協議。如果一個類或協議不屬于一個組的一部分,將它的聲明放在一個單獨的文件中,這個文件的名字是已經聲明過的類或者協議。
頭文件聲明 NSLocale.h NSLocale類 -
聲明相關的類和協議。對于一個含有相關聲明的組(類、分類和協議),將聲明放在一個與初始類、分類或協議命名相關的文件中。
頭文件聲明 NSString.h NSString和NSMutableString類 NSLock.h NSLocking協議和NSLock, NSConditionLock, 以及 NSRecursiveLock 類 -
包含框架頭文件。每一個框架應當有一個頭文件,在框架后面命名,這個頭文件應當包含所有的框架的公共頭文件。
頭文件聲明 Foundation.h Foundation.framework -
在其它框架中添加API到一個類中。如果你想要在一個框架中中聲明一個在其它框架中類的分類中的方法,請在原始類的命名后面追加“Additions”;一個典型的例子就是應用包中的NSBundleAdditions.h 頭文件。
-
相關的函數和數據類型。如果你有一組相關的函數、常量、結構體以及其它數據類型,可以將他們放在一個恰當命名的頭文件中,例如 NSGraphics.h(應用包)。
方法命名
方法或許是你編程接口中最常見的元素,因此在它們的命名方面應當特別小心。
一般規則
-
方法名應該以小寫字母打頭,然后緊接著后面每一個單詞的首字母大寫。不要使用前綴。可以參見前面的排滿約定。
對于以上指南,有兩個特別的例外,你可能用一個很有名的大寫縮寫詞為方法名打頭(例如 TIFF或PDF),而且你可能會使用前綴來標記組以及私有方法(參見上面的私有方法)。
-
當一個方法表示一個對象執行的動作的話,請以一個動詞打頭:
- (void)invokeWithTarget:(id)target; - (void)selectTabViewItem:(NSTabViewItem *)tabViewItem不要使用“do”或者“does”作為方法名的一部分,因為這些輔助的動詞很少能夠增強意思。同樣,絕不要在動詞前面使用副詞或者形容詞。
-
如果一個方法返回給調用者一個屬性的話,用這個屬性命名方法。沒有必要使用“get”。除非不是直接返回一個或多個返回值。
方法命名評述 - (NSSize)cellSize 正確 - (NSSize)calcCellSize 錯誤 - (NSSize)getCellSize 錯誤 -
在所有參數前面使用關鍵詞
方法命名評述 - (void)sendAction:(SEL)aSelector toObject:(id)anObject forAllCells:(BOOL)flag 正確 - (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag 錯誤 -
在參數之前描述這個參數
方法命名評述 - (id)viewWithTag:(NSInteger)aTag 正確 - (id)taggedView:(int)aTag 錯誤 -
當你想要創建一個比繼承的方法更加明確的方法的時候,請在方法結束之后,添加一些關鍵詞。
方法命名評述 - (id)initWithFrame:(CGRect)frameRect NSView,UIView - (id)initWithFrame:(NSRect)frameRect mode:(int)aMode cellClass:(Class)factoryId numberOfRows:(int)rowsHigh numberOfColumns:(int)colsWide NSMatrix, NSView的子類 -
不要在描述屬性的關鍵詞上使用“and”。
方法命名評述 - (int)runModalForDirectory:(NSString )path file:(NSString ) name types:(NSArray *)fileTypes 正確 - (int)runModalForDirectory:(NSString )path andFile:(NSString )name andTypes:(NSArray *)fileTypes 錯誤
盡管在這個例子中,采用“and”看起來還可以,但是當你創建越來越多關鍵詞方法的時候,會出問題。
-
如果一個方法描述了兩個單獨的動作,可以使用“and”來連接他們。
方法命名評述 - (BOOL)openFile:(NSString )fullPath withApplication:(NSString )appName andDeactivate:(BOOL)flag NSWorkspace
存取方法
存取方法是用來設置和返回一個對象的屬性值。有推薦的格式,取決于這個屬性表達方式:
-
如果這個屬性是名詞的話,格式是這樣的:
- (type)noun; - (void)setNoun:(type)aNoun;舉個例子:
- (NSString *)title; - (void)setTitle:(NSString *)aTitle; -
如果這個屬性是形容詞,格式是這樣的:
- (BOOL)isAdjective; - (void)setIsAdjective:(BOOL)flag;舉個例子:
- (BOOL)isEditable; - (void)setEditable:(BOOL)flag; -
如果這個屬性是一個動詞,格式是這樣的:
- (BOOL)verbObject; - (void)setVerbObject:(BOOL)flag;舉個例子:
- (BOOL)showsAlpha; - (void)setShowsAlpha:(BOOL)flag;動詞應當使用簡單的現在時。
-
不要通過分詞形式將動詞轉換成形容詞:
方法命名評述 - (void)setAcceptsGlyphInfo:(BOOL)flag 正確 - (BOOL)acceptsGlyphInfo 正確 - (void)setGlyphInfoAccepted:(BOOL)flag 錯誤 - (BOOL)glyphInfoAccepted 錯誤 -
你可能會使用情態動詞來闡述意思,但是不用使用“do” 或“does”。
方法命名評述 - (void)setCanHide:(BOOL)flag 正確 - (BOOL)canHide 正確 - (void)setShouldCloseDocument:(BOOL)flag 正確 - (BOOL)shouldCloseDocument 正確 - (void)setDoesAcceptGlyphInfo:(BOOL)flag 錯誤 - (BOOL)doesAcceptGlyphInfo 錯誤 -
僅當方法間接返回對象和值的時候,使用“get”命名。而且僅當多個條目需要返回的時候。
方法命名評述 - (void)getLineDash:(float *)pattern count:(int *)count phase:(float *)phase NSBezierPath 在以上這些方法中,方法的實現應當考慮針對這些輸入輸出參數可接受NULL值,來指明調用者不必對一個或者多個返回值感興趣。
代理方法
代理方法是指在某一事件發生的時候,一個對象調用它的代理(如果實現了這個代理)。它們有獨特的格式,同樣適用于對象數據源的方法調用。
-
方法打頭請標明是哪個類的對象發送信息的:
- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row; - (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename;類名省略前綴,并且第一個字母小寫。
-
一個冒號后附帶類名(參數是代理對象的實例),除非這個方法只有一個參數,發送者。
- (BOOL)applicationOpenUntitledFile:(NSApplication *)sender; -
有一個例外就是,方法作為通知被發送的結果來調用。這種情況下,這個單獨的參數就是通知對象。
- (void)windowDidChangeScreen:(NSNotification *)notification; -
在方法名上使用“did” 或 “will”用來通知代理,有事情已經發生或者即將發生。
- (void)browserDidScroll:(NSBrowser *)sender; - (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window; -
盡管你可以在方法名上使用“did” 或“will”,在方法被調用的時候,請求代理為另外一個對象做些事情,還是推薦“should”。
- (BOOL)windowShouldClose:(id)sender;
集合類的方法
對于那些管理對象集合的對象(每一個被稱為集合的元素),約定為以下方法格式:
- (void)addElement:(elementType)anObj; - (void)removeElement:(elementType)anObj; - (NSArray *)elements;例如:
- (void)addLayoutManager:(NSLayoutManager *)obj; - (void)removeLayoutManager:(NSLayoutManager *)obj; - (NSArray *)layoutManagers;以下是對這個指南的限制和改進:
- 如果這個集合的確是無序的,返回一個NSSet對象,而不是一個NSArray對象。
- 如果向一個集合中指定位置插入一個元素非常重要,采用以下形式的方法:- (void)insertLayoutManager:(NSLayoutManager *)obj atIndex:(int)index; - (void)removeLayoutManagerAtIndex:(int)index;
還有一些針對于MRC的說明,這里就不列舉了。在非ARC下,確定好需要強弱引用。如果是強引用,使用NSArray、NSDictionary, NSSet等;如果是弱引用,則使用NSPointerArray、NSMapTable、NSHashTable。
方法參數
這里有一些設計方法參數命名的一般規則:
- 和方法一樣,參數要以一個小寫字母打頭,并且后續每一個單詞的首字母要大寫(例如: removeObject:(id)anObject)。
- 不要在參數名中使用“pointer” 或“ptr”。讓參數的類型而不是它的名稱,聲明它是否是指針。
- 避免1-2個字母的參數名字。
- 避免縮寫
一般來說(在Cocoa中),以下關鍵詞和參數會同時使用:
...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 ...length:(int)numBytes ...point:(NSPoint)aPoint ...stringValue:(NSString *)aString ...tag:(int)anInt ...target:(id)anObject ...title:(NSString *)aString私有方法
在大多數情況下,私有方法名稱通常遵循與公共方法名稱相同的規則。然而,一個常見的約定就是給私有方法一個前綴,以便它很容易與公共方法進行區分。即使采用這種約定,私有方法的名稱也可能導致一種特殊類型的問題。當您設計Cocoa框架類的子類時,您無法知道您的私有方法是否無意中覆蓋了具有相同名稱的私有框架方法。
大多數Cocoa框架中的私有方法有一個下劃線前綴(例如:_fooData ),用來標記它是私有方法。基于這個事實,有以下兩個建議:
- 不要使用下劃線字符作為私有方法的前綴。Apple保留了這一慣例。
- 如果是一個大型Cocoa框架類(如NSView或UIView)的子類,你想要絕對確定你的私有方法的名稱與超類中的名稱不同,你可以在你的私有方法前面加上一個你自己的前綴。這個前綴盡可能的唯一,或許基于你的公司或者項目,諸如XX_格式。所以,若你的項目叫做Byte Flogger,那么這個方法名可以是BF_addObject: 。
盡管給私有方法加上前綴的命名方式看起來像是與先前的提出的方法以類為區分的命名空間存在矛盾之處,但是這里的意圖在于:防止無意識的重寫了父類的私有方法。
函數命名
Objective-C 允許你通過函數來實現和方法一樣的功能。當潛在的對象總是一個單例或者當你要處理明顯的功能性的子系統,你可以使用函數,而不是類中的方法。
函數有一些通用的命名規則,您應該遵循:
-
函數的命名格式有點類似方法的命名,但是有一些例外:
- 函數名以你使用的類或者常量前綴開始。
- 前綴后面的第一個單詞首字母大寫。
-
大多數的函數名以動詞打頭,用來表示這個函數的作用:
NSHighlightRect NSDeallocateObject
如果這個函數是用來查詢屬性的話,還有一套其它的規則:
- 如果你的函數返回的是第一個參數的屬性,請忽略掉動詞。unsigned int NSEventMaskFromType(NSEventType type) float NSHeight(NSRect aRect)
- 如果返回的是引用類型,請使用“Get”。const char *NSGetSizeAndAlignment(const char *typePtr, unsigned int *sizep, unsigned int *alignp)
- 如果返回的是布爾類型,函數應該以一個變化動詞打頭。BOOL NSDecimalIsNotANumber(const NSDecimal *decimal)
屬性和數據類型命名
這個部分介紹了聲明的屬性,實例變量,常量,通知和異常的命名約定。
聲明的屬性和實例變量
屬性的聲明實際上是聲明屬性的讀寫方法,所以聲明屬性的命名約定大體上和存取方法的命名約定相同(參見存取方法)。
-
如果屬性是一個名詞或者動詞,格式是:
@property (…) type nounOrVerb;例如:
@property (strong) NSString *title; @property (assign) BOOL showsAlpha; -
如果聲明屬性是一個形容詞,然而需要注意的是,屬性名忽略掉“is”前綴,但是指定約定的get讀取方法名,例如:
@property (assign, getter=isEditable) BOOL editable;
大多數情況下,當你使用一個聲明的屬性的時候,你同樣需要 synthesize 一個相對應的實例變量。
確保實例變量的名字簡明的描述存儲的屬性。通常情況下,你不能直接訪問實例變量;而是要通過讀寫方法來訪問(你可以在init和dealloc方法中直接訪問實例變量)。為了更加顯著,可以采用下劃線來命名實例變量,例如:
@implementation MyClass {BOOL _showsTitle; }如果你通過使用一個聲明屬性來synthesize一個實例變量的話,在@synthesize語句中指定實例變量的名字。
@implementation MyClass @synthesize showsTitle=_showsTitle; @end當向類中添加實例變量的時候,需要考慮以下幾點:
-
避免直接聲明公共的實例變量
開發者應當關注的是對象的接口,而不是如何存儲數據的細節。你可以通過使用聲明屬性以及synthesize相關的實例變量。
-
如果你需要聲明一個實例變量,避免用@private或者@protected來聲明。
如果您希望您的類被子類化,并且這些子類需要直接訪問數據,請使用@protected。
-
如果一個實例變量是一個類實例的可訪問屬性,確保你給他寫了讀寫方法(如果可能,請使用聲明屬性)。
常量
常量的命名規則根據常量的創建方式而有所不同。
枚舉常量
- 對具有整數值的相關常量組使用枚舉。
- 枚舉常量和類型定義組的命名約定遵循函數的命名(參見函數命名)。以下例子來自 NSMatrix.h:typedef enum _NSMatrixMode {NSRadioModeMatrix = 0,NSHighlightModeMatrix = 1,NSListModeMatrix = 2,NSTrackModeMatrix = 3 } NSMatrixMode; 注意類型定義的標簽(例如上面的_NSMatrixMode)是不需要的。
- 你可以創建一個諸如位掩碼的沒有名字的枚舉類型。例如:enum {NSBorderlessWindowMask = 0,NSTitledWindowMask = 1 << 0,NSClosableWindowMask = 1 << 1,NSMiniaturizableWindowMask = 1 << 2,NSResizableWindowMask = 1 << 3 };
通過const定義的常量
-
使用const為浮點值創建常量。如果常量與其他常量無關,則可以使用const創建整數常量;否則,請使用枚舉。
-
下面是一個const常量的命名格式的例子:
const float NSLightGray;如同常量枚舉一樣,對于函數的命名約定也是一樣的(參見函數命名)。
其他類型的常量
-
通常,不要使用#define預處理程序命令來創建常量。對于整數常量,使用枚舉;對于浮點常量,使用const限定符,如上所述。
-
可以用大寫符號做標記,這樣預處理器就可以判斷代碼是否需要被處理。例如:
#ifdef DEBUG -
注意,由編譯器定義的宏在頭部和尾部有雙下劃線。例如:
__MACH__ -
對于通知名和字典鍵,我們可以定義字符串常量。通過字符串常量,你可以確保編譯器檢驗指定的正確的值(是指執行拼寫檢查)Cocoa框架提供許多字符串常量的例子,例如:
APPKIT_EXTERN NSString *NSPrintCopies;實際上,NSString的值是賦值給一個實現文件中的常量的。(請注意,在Objective-C中,APPKIT_EXTERN宏是全局變量。)
通知和異常
通知和異常的命名約定遵守同樣的規則。但是又有他們自己推薦的使用規則。
通知
如果一個類有一個代理,大多數的通知有可能通過代理中定義的代理方法來接收的。這些通知的名稱應反映相應的委托方法。例如,一個全局的 NSApplication代理對象自動注冊接收一個applicationDidBecomeActive: 的消息,當application任何時候發送一個NSApplicationDidBecomeActiveNotification。
通知是通過全局的NSString對象來標識的,以以下方式來組合名字:
[Name of associated class] + [Did | Will] + [UniquePartOfName] + Notification例如:
NSApplicationDidBecomeActiveNotification NSWindowDidMiniaturizeNotification NSTextViewDidChangeSelectionNotification NSColorPanelColorDidChangeNotification異常
盡管你可以選擇任意意圖來使用異常(就是說NSException類和相關函數提供的機制)但是Cocoa保留一些編程錯誤的異常,例如:數組索引越界異常。Cocoa不使用異常來處理常規的預期錯誤情況。對于這些情況,請使用返回值,例如nil,NULL,NO或錯誤代碼的返回值。更多細節,請參見Error Handling Programming Guide。
異常是通過全局的NSString對象來標識的,以以下方式來組合名字:
[Prefix] + [UniquePartOfName] + Exception名稱的唯一部分應該將組成單詞組合在一起并將每個單詞的首字母大寫。以下是一些例子:
NSColorListIOException NSColorListNotEditableException NSDraggingException NSFontUnavailableException NSIllegalSelectorException可接受的縮寫和縮略語
一般來說,你在設計你的編程接口的時候,不應當縮寫名字(參見一般原則)。然而,以下列舉的縮寫詞已經創建并且在過去使用了很久,所以你可以繼續使用他。這里有額外的兩件事情需要注意:
- 簡寫格式和在標準的C庫中長期使用的格式相同的,例如:“alloc”和“getc”是允許的。
- 你在參數名中可以更加自由的使用簡寫(例如:imageRep, col (column的簡寫), obj, 和 otherWin)。
| alloc | Allocate 分配 |
| alt | Alternate 輪流的 |
| app | Application 應用 例如,全局的應用對象NSApp。然而在代理方法、通知等地方,還是用全拼“application”。 |
| calc | Calculate 計算 |
| dealloc | Deallocate 取消分配 |
| func | Function 函數 |
| horiz | Horizontal 水平 |
| info | Information 信息 |
| init | Initialize 初始化 用戶初始化對象方法中 |
| max | Maximum 最大 |
| min | Minimum 最小 |
| msg | Message 消息 |
| nib | Interface Builder archive 界面生成器歸檔文件 |
| pboard | Pasteboard 剪切板(僅在常量中使用) |
| rect | Rectangle 矩形 |
| Rep | Representation 表現(在類名中使用,例如NSBitmapImageRep)。 |
| temp | Temporary 臨時的 |
| vert | Vertical 垂直的 |
你可以使用已經在計算機工業領域非常常見的縮寫詞和首字母大寫詞。以下是一些比較有名的縮寫詞:
ASCIIPDFXMLHTMLURLRTFHTTPTIFFJPGPNGGIFLZWROMRGBCMYKMIDIFTP框架開發者的技巧和小結
框架開發人員在編寫代碼時必須比其他開發人員更加謹慎。很多的客戶端應用都會鏈接到他們的框架中,正是因為這樣廣泛的暴露,任何框架的缺陷,都會放大到整個系統。下面的內容討論一些你可以采納的編程技巧,用來確保你的框架的效率和完整性。
備注:一些技巧不僅僅局限于框架。你也可以應用到應用開發中。
初始化
以下是包含了框架初始化的意見和建議。
類初始化
+initialize方法為您提供了一個位置,可以在調用類的任何其他方法之前懶惰地執行一次代碼。通常被用來設置類的版本號(參見版本和兼容性)。
運行時將+initialize方法發送到繼承鏈中的每個類,即使它尚未實現它。因此它可能不止一次地調用類的+initialize方法(例如,如果子類沒有實現它)。通常,您希望初始化代碼只執行一次。確保這種情況發生的一種方法是使用dispatch_once():
+ (void)initialize {static dispatch_once_t onceToken = 0;dispatch_once(&onceToken, ^{// the initializing code} }備注:因為運行時會發送初始化給每一個類,很有可能initialize在子類的上下文環境中調用。如果子類沒有實現initialize,然后這個調用會傳到父類。如果你需要在相關的類的上下文中執行初始化,你可以用以下檢查替代 dispatch_once()的使用會更好:
if (self == [NSFoo class]) {// the initializing code }絕不要顯示的調用initialize方法。如果你想要觸發初始化,調用一些無害的方法,例如:
[NSImage self];指定的初始化函數
指定的初始化函數是調用父類的init方法的類的init方法。(其它初始化器調用類定義的初始化方法)。每個公共類都應該有一個或多個指定的初始化函數。指定初始化方法的例子有:NSView類的initWithFrame:和NSResponder的init方法。init方法并不意味著需要重寫,比如NSString類和其它類簇中的抽象類,子類應該來自己實現。
指定的初始化函數應當明確的指定,因為這對于想要依據你的類創建子類來說非常重要。子類僅重寫指定的初始化函數。
當你要實現一個框架的類時,你經常需要也實現它的存檔方法:initWithCoder:和 encodeWithCoder:。注意不要當在對象解歸檔的時候,在初始化的代碼中執行不會發生的事情。實現這一目標的一個好方法是,如果您的類實現了歸檔,則從您指定的初始化程序和initWithCoder調用一個公共例程:(這是一個指定的初始化函數)。
初始化過程中的錯誤檢測
一個設計比較好的初始化方法應當通過完成以下幾部來確保正確的檢測和錯誤輸出:
版本和兼容性
向框架添加新類或方法時,通常不必為每個新功能組指定新版本號。開發人員通常執行(或應該執行)Objective-C運行時檢查,例如respondsToSelector:以確定某個功能是否在給定系統上可用。這些運行時測試是檢查新功能的首選和最動態的方法。
但是,您可以使用多種技術來確保正確標記每個新版本的框架,并使其與早期版本盡可能兼容。
框架版本
當有一個新增特性或者bug被修復時,通過運行時測試是不容易檢測出來的,你應當以某種方式告知開發者來檢測這種變化。一種方式就是以歸檔的形式來存儲框架的確切版本號,同時需要讓開發者可見這些內容:
- 在每一個版本號下做文檔記錄變化(例如,在發布備注中)。
- 設置你框架當前的版本號并且提供一些方式讓它能夠全局訪問。你可以通過Info.plist文件來存儲你框架的版本號,然后可以通過這種方式來訪問。
歸檔中的key
如果你的框架對象需要寫入nib文件,他們必須能夠自歸檔。你同樣需要通過使用歸檔機制存儲文檔數據來歸檔任何文檔。
你應當考慮以下關于歸檔方面的問題:
- 如果歸檔中的key丟失了,請求對應的值的話,將會返回nil、NULL、NO、0或0.0等,取決于請求的數據類型。通過判斷這個返回值,可以減少你的數據輸出。另外,你可以確認這個key有沒有被寫入歸檔。
- 編碼和解碼方法都可以做到確保向后兼容性。例如一個類的新版本的編碼方法可能會通過使用key寫入新的值,但是可能仍然返回舊的字段以便舊版本的類仍然知道這個對象。另外,解碼方法想要通過一些可能的方式來處理丟失的值來保持新版本的靈活性。
- 對于框架類的歸檔key的一個推薦的命名約定就是以針對于其他框架API元素的前綴并且使用實例變量的名字。這樣確保命名不會和其他任何父類或子類的名字沖突。
- 如果你有一個工具函數輸出一個基本的數據類型(換言之,這個值不是對象),確保使用一個唯一的key。例如,如果你有一個archiveRect程序來歸檔舉行,需要傳入key參數,你可以使用它。或者,如果它輸出多個值(例如,四個浮點數據),應當在給定的key上追加自己唯一的位。
- 按照原樣來歸檔位字段是很危險的,因為這個和編譯器以及字節順序依賴有關。你僅能在對于優化的原因的情況下歸檔位字段,例如,需要大量多次的的位輸出。參見位字段。
異常和錯誤
大多數Cocoa框架方法都不會強制開發人員捕獲和處理異常。這是因為異常不是作為執行的正常部分引發的,并且通常不用于傳達預期的運行時或用戶錯誤。這些錯誤的示例包括:
- 文件沒有找到
- 沒有此用戶
- 在應用中視圖打開一個錯誤的文檔類型
- 轉化字符串到特定編碼格式錯誤
然而,Cocoa對于以下情況會產生異常來指明程序或者邏輯錯誤:
- 數據越界訪問
- 嘗試改變不可變的對象
- 錯誤的參數類型
期望開發人員在測試期間捕獲這些錯誤并在發布應用程序之前解決它們;因此,應用程序不需要在運行時處理異常。如果一個異常往外擴散,應用沒有捕獲它,高級別的默認處理器通常會處理它,并且會報告異常,然后讓它們繼續執行。開發人員可以選擇將此默認異常捕獲器替換為提供更多錯誤詳細信息的異常捕獲器,并且提供一個可選項來保存數據并且退出應用程序。
錯誤是Cocoa框架與其他軟件庫不同的另一個領域。Cocoa方法通常不返回錯誤代碼。在存在一個合理或可能的錯誤原因的情況下,這些方法依賴于對布爾或對象(nil / non-nil)返回值的簡單測試;記錄NO或零返回值的原因。您不應該使用錯誤代碼來指示要在運行時處理的編程錯誤,而是引發異常,或者在某些情況下只記錄錯誤而不引發異常。
例如,NSDictionary的objectForKey:方法要么返回找到的對象,要么返回nil,如果它找不到對象。NSArray的objectAtIndex:方法不會返回nil(除非重寫一般語言約定,將任何信息轉換成nil,導致返回nil),因為NSArray對象不能存儲nil值,并且根據定義,任何越界訪問都是一個編程錯誤,應該導致異常。許多初始化方法會因為通過提供的參數不能夠初始化,從而導致返回nil。
在少數情況下,一個方法有一個返回多個不同的錯誤代碼,應當用引用參數來指定它,返回一個錯誤的代碼,一個本地話的錯誤字符串,或者其他的描述錯誤的信息。例如,你肯能需要返回一個NSError對象來表示錯誤;可以查看框架中的NSError.h頭文件來獲取細節。這個參數一般來說是一個直接返回的BOOL或者nil。該方法還應遵守以下約定:所有引用參數都是可選的,因此如果發件人不希望了解錯誤,則允許發送者為錯誤代碼參數傳遞NULL。
框架數據
如何處理框架數據會對性能,跨平臺兼容性和其他目的產生影響。這部分討論涉及的框架數據的技巧。
常量數據
出于性能原因,最好將盡可能多的框架數據標記為常量,因為這樣做會減小Mach-O二進制文件的__DATA段的大小。非const的全局和靜態數據最終會出現在__DATA section的__DATA段中。這種數據占用了使用框架的應用程序的每個運行實例中的內存。雖然額外的500字節(舉個例子)可能看起來不那么糟糕,但它可能會導致所需頁數增加 - 每個應用程序額外增加4千字節。
您應該將任何常量數據標記為const。如果block中沒有char *指針,會導致數據處在__TEXT段中(這里會使之成為真正的常量);否則的話,它處于__DATA段,但是卻不能寫入(除非預綁定沒有完成或者通過在加載時二進制的偏移來改變它)。
您應該初始化靜態變量以確保它們合并到__DATA段的__data部分而不是__bss部分。如果沒有明顯的值用于初始化,請使用0,NULL,0.0或任何適當的值。
位段
針對位段使用有符號的值,特別是一位的位段,這樣會導致如果代碼將這個值作為boolean值,會出現未定義行為。一位位域應始終為無符號。因為可以存儲在這樣的位域中的唯一值是0和-1(取決于編譯器實現),所以將該位域與1進行比較是錯誤的。例如,如果您在代碼中遇到類似這樣的內容:
BOOL isAttachment:1; int startTracking:1;您應該將類型更改為unsigned int。
另外一個和位段相關的內容是歸檔。一般來說,你不能以位段本身的格式來寫入到磁盤或者歸檔中,因為當在另外一個架構或者其它編譯器上讀取的時候,格式可能不同。
內存分配
在框架代碼中,避免全部內存分配是最好的課程。如果由于某種原因需要臨時緩沖區,通常使用棧比分配緩沖區更好。但是,堆棧的大小有限(通常總共512KB),因此使用堆棧的決定取決于您需要的函數和緩沖區的大小。通常,如果buffer大小是1000bytes(或者MAXPATHLEN)或者更小,可以使用棧。
一個改進是開始使用棧,但如果大小要求超出棧緩沖區大小,則切換到堆緩沖區中。以下有例子:
#define STACKBUFSIZE (1000 / sizeof(YourElementType))YourElementType stackBuffer[STACKBUFSIZE]; YourElementType *buf = stackBuffer; int capacity = STACKBUFSIZE; // In terms of YourElementType int numElements = 0; // In terms of YourElementTypewhile (1) {if (numElements > capacity) { // Need more roomint newCapacity = capacity * 2; // Or whatever your growth algorithm isif (buf == stackBuffer) { // Previously using stack; switch to allocated memorybuf = malloc(newCapacity * sizeof(YourElementType));memmove(buf, stackBuffer, capacity * sizeof(YourElementType));} else { // Was already using malloc; simply reallocbuf = realloc(buf, newCapacity * sizeof(YourElementType));}capacity = newCapacity;}// ... use buf; increment numElements ...}// ...if (buf != stackBuffer) free(buf);對象比較
你應當意識到泛型的對象比較方法isEqual: 和對象相關的比較方法,例如isEqualToString:方法之間的重要區別。isEqual: 方法允許你傳入任意對象作為參數,并且如果對象不是同一個類會返回NO。諸如isEqualToString: 和isEqualToArray:方法通常假設參數是指定的類型(也就是接收者的類型)。因此,它們不執行類型檢查,因此它們更快但不安全。對于從外部源檢索的值,例如應用程序的信息屬性列表(Info.plist)或首選項,使用isEqual:是首選,因為它更安全;當類型已知時,請使用isEqualToString:代替。
和isEqual:方法相關的一點就是它和hash方法有關。對象一個最基本的不變的地方就是被放入一個基于哈希的例如NSDictionary或NSSetCocoa集合中,如果[A isEqual:B] == YES,那么[A hash] == [B hash]。如果你重寫你的類的isEqual:方法,你應當也要重寫hash方法來維持這一個不變的條件。默認的isEqual:方法尋找和每一個對象地址相等的指針,并且hash方法返回的hash值是基于每一個對象的地址,所以還是保持了這個不變性。
總結
- 上一篇: mpeg4 码流格式及判断关键帧
- 下一篇: 笔试题集锦