Cocoa編碼規範--前言用公用API開發一個Cocoa架構,外掛程式,或其他可執行目標,裡面的命名編寫和規範不同於一般應用程式的開發。因為你開發出來東西是給開發人員用的看的,並且他們不熟悉你的編程介面。這個時候API的命名規範就派上用場了,因為它使你的寫的介面清楚明確。本文檔裡還包括開發架構中一些特殊的/重要的編程技術,例如:版本控制,二進位相容性,錯誤處理,和記憶體管理。本文檔包含倆部分內容:Cocoa命名規範、一些架構的編程實踐。--文檔組織本文檔內容分類倆大部分:第一部分是主要內容,為編程介面的命名規範。這些規範(有一些小的例外)也是Apple開發自己架構所用的規範。這些命名規範具體體現在以下幾個方面:“基本代碼命名”、“方法命名”、“函數命名”、“屬性和資料類型命名”、“通用的縮寫和簡稱”。 第二塊部分討論架構編程方面:“使用架構中的一些技巧和技術”。
註:本文章只翻譯前一部分內容,即:Cocoa命名規範
=========================================章節分割線=========================================
--基本命名規則在面對對象軟體庫開發中,一個經常被忽視的問題是:類、方法、函數、常數等編程介面的命名問題。這一章主要討論Cocoa介面中常用代碼的命名規範。
--一般規則--明確
代碼越簡潔越明確越好,但是不能因為簡潔而導致語義不明確:
| 代碼 |
評價 |
| insertObject: atIndex: |
好 |
| insert:at: |
不明確,什麼被插入?at指什麼 |
| removeObjectAtIndex: |
好 |
| removeObject: |
好,沒有之前討論的那些問題 |
| remove: |
不明確,什麼被移除了 |
通常,不要縮寫對象的名稱。即使它們很長,也全拼:
| 代碼 |
評價 |
| destinationSelection |
好 |
| destSel |
不明確 |
| setBackgroundColor: |
好 |
| setBkgdColor: |
不明確 |
--你可能認為某些縮寫是眾所周知的。但凡是無絕對,尤其是當開發人員和你文化、語言背景不一樣,看這些縮寫就可能產生歧義。
--當然,一些公認的通用的縮寫,還是可以用。參考“通用的縮寫”章節
在API的命名上避免歧義,例如方法的名字可以有多個解釋:
| code |
評價 |
| sendPort |
是傳送埠還是返回連接埠 |
| displayName |
它是顯示一個名字還是在使用者介面返回訊息寄件者的標題 |
--一致性
始終使用Cocoa 編程介面的名字。如果你不太確定,查看已有的標頭檔和參考文檔。
一致性非常重要,當你有個實現多態方法的類。不同類中處理同一個任務的方法應該擁有同樣的名稱。
| code |
評價 |
| - (NSInteger)tag |
在 NSView, NSCell, NSControl類中都要定義 |
--不要自我引用(Self Reference)命名不應該自我重複。
| code |
評價 |
| NSString |
okay |
| NSStringObject |
自我引用 |
常數(看做標記,可以進行位元運算)不適用這個規則,例如用作通知名稱。
| code |
評價 |
| NSUnderlineByWordMask |
okay |
| NSTableViewColumnDidMoveNotification |
okay |
--首碼首碼在編程介面中是非常重要的一部分。一個軟體有不同的功能模組,通常它們封裝在一個架構或者相近的架構中。首碼避免了第三方開發人員和Apple之間的命名衝突。 --首碼有規定的格式。通常由2/3個大寫字母組成,不是用底線和子首碼。例如:NS、IB、AB。 --使用首碼來命名類、協議、函數、常數,自訂資料類型(typedef structures),不要用首碼來命名方法。方法存在類的命名地區中,不要在這地區裡面使用首碼。--書寫規則對於由多個單片語成的名字,不要使用標點符號(底線、破折號等)作為名稱部分或作為分隔字元。相反每個單詞第一個字母大寫並且連著寫--
駱駝風格。注意一下幾點: --對於方法名字,小寫字母開頭,不要用首碼。一個例外是,方法名稱以通用的縮寫開頭,例如:TIFFRepresentation (NSImage)。
--對於
函數和常數,和相關類使用相同的首碼,並且大寫第一個字母。例:NSRunAlertPanel、NSCellDisabled。 --避免使用底線作為首碼意義在於會導致方法名稱私人的意思(可以用它做執行個體變數)。Apple保留使用該規則。但在第三方用可能導致命名衝突,他們會不自覺的重寫自己已有的一個私人方法。參考“私人方法”章節。--類和協議名稱一個類的名稱應該
包含一個名詞,清楚地表明的類(或類的對象)作用或者意義。名稱應該有一個適當的首碼(參考“首碼”小節)。在架構中類名的例子比比皆是:NSString,NSDate,nsscanner,NSApplication,UIApplication,NSButton, and UIButton。
協議的命名應該根據使用協議的相應類行為命名。 --大多數協議包含的相關方法,不與任何特定的類關聯。這種協議的應該命名為使協議與類不能混淆。一個通常的規則是
用動名詞(...ing)。對比NSLocking 、NSLock(看起來像類名)。 --有的協議包含一些沒什麼聯絡的方法(而不是建立多個獨立的小協議)。這些協議跟一個類的聯絡很大,這個類主要體現了這個協議。這種情況下,命名規則為協議名
跟類名字一樣。一個例子是NSObject 協議,這個協議包含一些方法可以查詢任何類在父類中的層次位置等。因為NSObject類實現了協議的大部分方法,所以協議可以以類名命名。
--標頭檔怎麼命名你的標頭檔非常重要。因為你的命名表明了類中的內容: --
聲明一個獨立的類/協議:如果一個類/協議不是一個檔案中的一部分,將其聲明獨立成一個檔案,這個檔案的名字表明了該類/協議;
--
聲明聯絡的類/協議:如果有一些聯絡的聲明(類、協議、分類),將它們聲明放到一個檔案中,檔案的命名根據基礎的類、協議、分類;
| 標頭檔 |
聲明 |
| NSString.h |
NSString和NSSMutableString |
| NSLock.h |
NSLocking協議、NSLock、NSConditionLock、NSRecursive類 |
-
-包含架構的標頭檔:所有的架構都有一個標頭檔,以架構命名,包含架構裡所有公開的標頭檔。例Foundation.h-- Foundation.framwork。
--為別的架構中類增加API:如果你在一個架構中聲明的方法,是另一個架構中類的分類,名字為原來類的名字拼接上“Additions”。一個例子為Applicatiion kit 的NSBuddleAdditions.h標頭檔。 --相聯絡的函數和資料類型:如果你有一些相聯絡的函數、常數、結構體等其他資料類型,將它們放到合適命名的標頭檔中。例如NSGraphics.h(Applicatiion kit )。 =========================================章節分割線=========================================
--方法命名方法在編程介面中非常常見,所以命名方法要重視。這章主要討論方法命名的以下幾個方面:--基本規則當為方法命名時,要記住以下幾點:
----方法名小寫開頭,之後每個單字首大寫(Camel-Case),不要用首碼; 有倆種情況例外。如果方法用到了眾所周知的縮寫(例如TIFF或PDF) ;你可能使用首碼去統一定義私人方法,參考“私人方法”小節。
----如果方法代表對象某個動作,方法名用動詞開頭; 例如:- (void)invokeWithTarget:(id)target;
不要使用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; |
UIView |
| - (id)initWithFrame:(NSRect)frameRect mode:(int)aMode cellClass:(Class)factoryId numberOfRows:(int)rowsHigh numberOfColumns:(int)colsWide; |
NSMatrix, a subclass of NSView |
----不要使用and去串連多個參數的關鍵詞(對象屬性名稱)
| - (int)runModalForDirectory:(NSString *)path file:(NSString *) name types:(NSArray *)fileTypes; |
right |
| - (int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes; |
wrong |
儘管在這個例子中and看起來還不錯,但是當方法中有許多參數的時候,再用and就不行了。
----如果方法包含著倆個分開的動作,用and去串連它們;例:- (BOOL)openFile:(NSString *)fullPath withApplication:(NSString *)appName andDeactivate:(BOOL)flag;--存取器方法存取器放方法是指那些讀/寫對象屬性的方法,根據屬性意義的不同,它們有不同的通用格式。(備忘:不同格式代表不同對應執行個體變數的寫法,存取器方法形式就是intanceVariables 和 setIntanceVariables倆種形式)
----如果屬性工作表示的是名詞意思,格式如:
- (type)noun; - (void)setNoun:(type)aNoun;
----如果屬性工作表示的是形容詞意思,格式如:
- (BOOL)isAdjective; - (void)setAdjective:(BOOL)flag; (注意type是BOOL)
例:- (BOOL)isEditable; - (void)setEditable:(BOOL)flag; ----如果屬性工作表示的是動詞意思 , 格式如:
(BOOL)verbObject; - (void)setVerbObject:(BOOL)flag; (注意type為BOOL)
例:- (BOOL)showsAlpha; - (void)setShowsAlpha:(BOOL)flag; 動詞是現在時;
----在屬性的名稱中,不要通過用分詞形式將動詞轉換為形容詞;
| - (void)setAcceptsGlyphInfo:(BOOL)flag; |
right |
| - (BOOL)acceptsGlyphInfo; |
right |
| - (void)setGlyphInfoAccepted:(BOOL)flag; |
wrong |
| - (BOOL)glyphInfoAccepted; |
wrong |
----
可以使用情態動詞(動詞前面“can”、“should”、“will”等)進一步說明屬性意思,但不要使用'do'或'does'。
| - (void)setCanHide:(BOOL)flag; |
Y |
| - (BOOL)canHide; |
Y |
| - (void)setShouldCloseDocument:(BOOL)flag; |
Y |
| - (BOOL)shouldCloseDocument; |
Y |
| - (void)setDoesAcceptGlyphInfo:(BOOL)flag; |
N |
| - (BOOL)doesAcceptGlyphInfo; |
N |
----
當使用get這個詞時,只有當方法間接返回多個對象/值。- (void)getLineDash:(float *)pattern count:(int *)count phase:(float *)phase; 注意,這種形式的方法,其中的引用型參數應該能接收NULL,因為方法調用者可能並不需要多個傳回值。--代理方法代理方法是那些當發生特定事件對象使用它delegate調用的方法(如果delegate實現了它),它們有著特定的格式,這些格式也適用於對象的datesource方法。
----名字的開頭指明發訊息的物件類型。例如:- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row; - (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename;
類名省略了它的首碼並且小寫開頭。 ----
如果方法只有一個參數,格式為:冒號+類名(調用代理的對象)+sender;例:- (BOOL)applicationOpenUntitledFile:(NSApplication *)sender; ----
一個例外是方法用來發送通知,如果這樣的話,方法參數為通知對象;例:- (void)windowDidChangeScreen:(NSNotification *)notification;
-----命名中使用did或will這類詞,告訴delegate某些事情已經發生或將要發生;例:- (void)browserDidScroll:(NSBrowser *)sender; ----
雖然你可以在命名中是使用did或will這類詞,告訴delegate去做某些事情,但有時“should”更合適;例:- (BOOL)windowShouldClose:(id)sender;--集合方法(Collection Method)要管理對象(每一個叫做對象的元素)的集合,命名方法以下格式:
- (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; 以下一些實現細節要注意:
----這些方法通常暗含插入對象的擁有權(ownership)的管理,所以添加/插入元素的時候retain它們,移除的時候remove它們; ----
如果插入對象想保持它原來的持有的對象,通常對該對象的setter方法不用retain,例如insertLayoutManager:atIndex: method方法。NSLayoutManager類在以下的方法中同樣這樣處理:- (void)setTextStorage:(NSTextStorage *)textStorage; - (NSTextStorage *)textStorage; 通常你不用調用setTextStorage方法,但是你可能需要重寫它。 ----(這段難理解,上面屬於個人見解,參考原文:If the inserted objects need to have a pointer back to the main object, you do this (typically) with a set...method that sets the back pointer but does not retain. In the case of the insertLayoutManager:atIndex: method, the NSLayoutManager class does this in these methods: - (void)setTextStorage:(NSTextStorage *)textStorage; - (NSTextStorage *)textStorage; You would normally not call setTextStorage: directly, but might want to override it.) 以上說的集合方法的規則在NSWindow類中都有例子:
| - (void)addChildWindow:(NSWindow *)childWin ordered:(NSWindowOrderingMode)place; |
| - (void)removeChildWindow:(NSWindow *)childWin; |
| - (NSArray *)childWindows; |
| - (NSWindow *)parentWindow; |
| - (void)setParentWindow:(NSWindow *)window; |
----方法參數在命名方法參數時候有幾個基本規則:
----參數的名字也是駱駝風格
----不要使用“pointer”或ptr這些詞,參數的類型比參數的名字更能說明它是否是指標。
----避免一倆個字母做參數的名字
----避免縮寫,參數名不差多這幾個字母。
一般來講,以下的一些方法中的關鍵詞通常跟固定的參數搭配:
...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
----私人方法在大多數情況下,私人的方法名稱一般跟公用方法的名稱都遵循同樣的規則作為。然而,還有一個普遍的規則是給私人方法一個首碼,所以很容易區分他們跟公用方法。
即使遵循這些規則,私人方法名稱還是可以引起一些特殊問題。當你你編寫的Cocoa架構類的子類,你不知道你的私人方法是否無意中重寫方法裡同名稱的私人方法。 在Cocoa架構中命名大多數私人的方法用底線首碼開頭(例如,_foodata),標記方法為私人。對於這條規則,有兩個建議:
-----對於你自己的私人方法,不要使用底線首碼。Apple約定了這條規則;
-----如果是一個大cocoa架構類(如NSView)的子類,你要絕對確保你的私人的方法不同於父類的方法,您可以通過添加你自己專屬的首碼來區分。首碼應儘可能的唯一的,也許是一個基於在你公司或項目的形式”xx_”。所以如果你的項目被稱為Byte Flogger,首碼可以是BF_addobject;
雖然之前建議用首碼給私人方法命名,這看起來跟之前說的規則矛盾。但這塊情況特殊,我們必須確保子類無意間重寫父類的私人方法。 =========================================章節分割線=========================================
---函數命名Objective-c中實現一個功能可以通過函數和方法。當你的對象是單一實例或者處理一個子功能時候,更適合用函數。 函數命名有幾下基本原則:
----函數名類似方法名,但有一些例外: 它們用你在類/常數中的首碼開頭,並且首碼後的首字母大寫。
----許多函數名字已動詞開頭,描述函數實現的功能:NSHighlightRect NSDeallocateObject ----函數如果是查詢一些屬性,命名有一些特別的規定:
1.如果函數返回第一個參數的屬性,省略動詞:unsigned int NSEventMaskFromType(NSEventType type) float NSHeight(NSRect aRect)
2.如果函數傳回值是指標,使用Get:const char *NSGetSizeAndAlignment(const char *typePtr, unsigned int *sizep, unsigned int *alignp)
3.如果函數傳回值是布爾型,函數名用變化的(inflected)動詞開頭:BOOL NSDecimalIsNotANumber(const NSDecimal *decimal) =========================================章節分割線=========================================
--屬性和資料類型命名這部分講命名屬性、執行個體變數、常數、通知、異常。----屬性和執行個體變數命名因為屬性和存取器方法的對應性質(get方法和set後那部分名稱即是屬性名稱字,對應執行個體變數),所以對於屬性的命名基本類似存取器方法的名字,參考存取器方法小節。 ----
如果屬性是名稱/動詞意思,格式是:@property (…) type nounOrVerb 例:@property (strong) NSString *title; @property (assign) BOOL showsAlpha; -----
如果屬性是形容詞意思,屬性名稱省略is首碼,並且指定存取器get方法的命名。例如:@property (assign, getter=isEditable) BOOL editable; 在許多情況,當你聲明一個了屬性,
你同時也確定(synthesize)了相應的執行個體變數。 確保執行個體變數簡明的描述儲存的屬性,通常你不直接存取執行個體變數,而是通過存取器方法(在類內部直接存取),為了區別,
用底線首碼;例:
@implementation MyClass {BOOL _showsTitle;}如果你想用某執行個體變數對應某個屬性,在@synthesize中說明:@implementation MyClass @synthesize showsTitle=_showsTitle; 當添加執行個體變數的時候,有以下幾條規則:
-----避免顯示的聲明公用的執行個體變數。開發人員只會關心對象的介面,不關心實現的細節。通過聲明屬性和相應的(synthesizing)執行個體變數,避免顯示聲明執行個體變數。
-----如果需要聲明執行個體變數,用@private 或者 @protected聲明。如要繼承的執行個體變數用@protected聲明。
-----如果一個執行個體變數是執行個體的訪問屬性(accessible attribute),確保你已經寫了相應的存取器方法。--常數常數的命名規則跟常數是怎麼產生的息息相關。--枚舉常數
----對於有取值相聯絡的常數集合,使用枚舉(說什麼情況使用枚舉,跟命名沒關係)
----枚舉常數和typedef後面枚舉名的命名跟函數的命名規則類似,參考函數命名小節。例:
typedef enum _NSMatrixMode {NSRadioModeMatrix = 0,NSHighlightModeMatrix = 1,NSListModeMatrix = 2,NSTrackModeMatrix = 3} NSMatrixMode;注意上文中_NSMatrixMode 在typedef中沒有用。
----你也可以使用不命名的枚舉,比如位元遮罩(bit masks),例如:
enum {NSBorderlessWindowMask = 0,NSTitledWindowMask = 1 << 0,NSClosableWindowMask = 1 << 1,NSMiniaturizableWindowMask = 1 << 2,NSResizableWindowMask = 1 << 3};
--const修飾的常數
-----使用const去建立浮點型常量。可以建立整形常量,如果各整形常量之間沒有什麼聯絡,否則,使用枚舉。
----const修飾的常數命名規則,舉例說明:const float NSLightGray; 命名規則類似函數,參考函數命名小節。
--其他類型常數
----通常不使用#define先行編譯命令去建立常數。像上文說的,整形常數用枚舉,浮點型常數用const修飾。
----使用大寫字母符號讓編譯器決定某段代碼是否編譯。例如:#ifdef DEBUG
----注意由編譯器定義的宏,有前後各倆個底線。例如:__MACH__;
----定義字串常數,例如作方法名或字典的key等,你要確保編譯器識別字串常數(編譯語法檢查)。Cocoa提供了許多字串常量例子,如:APPKIT_EXTERN NSString *NSPrintCopies; 字串的值被指定了常量(注意APPKIT_EXTERN 宏在Objective-C中的像extern聲明的作用)
--通知和異常通知和異常的命名規則基本相同,但它們有各自特點。--通知
如果一個類有delegate,許多通知都會被delegate接收通過delegate方法。這些通知的名稱應該反應相應的delegate方法。例如,一個全域的NSApplication類對象自動註冊去接收applicationDidBecomeActive訊息,當應用程式發送NSApplicationDidBecomeActiveNotification.訊息的時候。通知通過全域的字串對象定義,格式如下:[Name of associated class] + [Did | Will] + [UniquePartOfName] + Notification; 例:
NSApplicationDidBecomeActiveNotificationNSWindowDidMiniaturizeNotificationNSTextViewDidChangeSelectionNotificationNSColorPanelColorDidChangeNotification
--異常
儘管你可以為了一些目的自由的使用異常(由NSException類和相關函數提供),Cocoa將編程中出現錯誤,例如數組越界,看做異常。Cocoa不使用異常去處理常規的、預料的錯誤情況,例如,傳回值為nil、NULL、NO或一些錯誤碼。詳細參考《Error Handling Programming Guide》。
異常通過全域的字串對象定義,格式如下:[Prefix] + [UniquePartOfName] + Exception; 其中unique part of the name是由單片語合而成,每個首字母大寫。例如:
NSColorListIOExceptionNSColorListNotEditableExceptionNSDraggingExceptionNSFontUnavailableExceptionNSIllegalSelectorException
=========================================章節分割線=========================================
--通用的縮寫和簡稱
通常你不用縮寫你的命名,當你編寫介面時候。參考基本命名規則章節。然而,下面所列舉的縮寫都是眾所周知的,你可以繼續使用它們。有以下幾點需要注意:
----縮寫的替代格式使用在標準C語音庫中被允許。例如:“alloc” and “getc”。
----你可以在參數中更自由的使用縮寫。例如:“imageRep”, “col” (for “column”),“obj”, and “otherWin”
| 縮寫 |
意義 |
| alloc |
Allocate |
| alt |
Alternate |
| app |
應用程式,例, NSApp全域應用程式物件。 “application” 全拼在delegate方法、通知中等 |
| calc |
Calculate. |
| dealloc |
Deallocate. |
| func |
Function. |
| horiz |
Horizontal. |
| info |
Information |
| init |
Initialize |
| max/min |
Maximum/Minimum. |
| msg |
Message |
| nib |
Interface Builder archive. |
| pboard |
Pasteboard (but only in constants |
| rect |
Rectangle. |
| Rep |
Representation (used in class name such as NSBitmapImageRep |
| temp |
Temporary. |
| vert |
Vertical. |
你也可以使用一些電腦行業通用的縮寫,例:
ASCII、PDF、XML、HTML、URL、RTF、HTTP、TIFF、JPG、PNG、GIF、LZW、ROM、RGB、CMYK、MIDI、FTP