Cocoa编码指南
翻译自“Coding Guidelines for Cocoa”
0 简介
使用公开的API开发一个Cocoa框架,插件,或者其它可执行文件需要的方法和规范,跟开发应用程序的不一样。产品的主要用户是开发者,他们不会被编程接口迷惑。此时API的命名规范就派上了用场,它们让接口保持清晰和一致性。同时它们也是编程技术,尤其对框架,例如版本控制,二进制兼容性,错误处理和内存管理。这个专题同时包括Cocoa命名规范和推荐的框架编程实践。
0.1 文档结构
本专题的文章分为两类。第一类是编程接口的命名规范。这些规范与Apple自己的Cocoa框架一样(有一些小的区别)。下面是命名规范的文章:
代码命名基础
方法命名
函数命名
属性和数据类型命名
可接受的缩写和首字母缩写
第二部分讨论框架编程相关的规范。
框架开发者的技巧和技术
1 代码命名基础
设计面向对象软件库时,类,协议,方法,函数,常量,以及其它元素的命名经常被忽略。这一节讨论大部分Cocoa接口的命名规范。
1.1 一般性原则
清晰
- 尽可能同时保持清晰和简洁,但不要因为简洁而牺牲清晰:
代码 | 点评 |
---|---|
insertObject:atIndex: | 好 |
insert:at: | 不清晰;插入什么?“at”表示什么? |
removeObjectAtIndex: | 好 |
removeObject: | 好,因为参数指定了要移除的对象 |
remove | 不清晰;要移除什么? |
- 通常不要使用名字的缩写。即使名字很长,也要拼写完全:
代码 | 点评 |
---|---|
destinationSelection | 好 |
destSel | 不清晰 |
setBackgroundColor: | 好 |
setBkgdColor: | 不清晰 |
你可能会认为某个缩写广为人知,但可能并非如此,尤其是当方法或函数名被不同文化和语言背景的开发人员使用时。
可以使用少数非常常见和历史悠久的缩写。参考“可接受的缩写和首字母缩写”一节。
避免使用有歧义的API名称,例如有多个解释的方法名。
代码 | 点评 |
---|---|
sendPort | 发送端口还是返回一个发送端口? |
displayName | 显示名称还是返回用户界面中接收者的标题? |
一致性
尽可能使用与Cocoa编程接口一致的名称。如果不确定某个命名,请浏览当前的头文件或参考文件中的范例。
当类的方法使用多态时,一致性尤其重要。不同类中实现相同功能的方法应该有相同的名称。
代码 | 点评 |
---|---|
- (NSInteger)tag | 在NSView, NSCell, NSControl中有定义 |
*- (void)setStringValue:(NSString ) | 在许多Cocoa类中有定义 |
参考“方法参数”一节。
不要自我参考
- 名称不应该自我参考。
代码 | 点评 |
---|---|
NSString | 可以 |
NSStringObject | 自我参考 |
- 掩码(可使用位操作进行组合)和用于通知名称的常量不受该约定限制。
代码 | 点评 |
---|---|
NSUnderlineByWordMask | 可以 |
NSTableViewColumnDidMoveNotification | 可以 |
1.2 前缀
前缀是名称的重要组成部分。它们可以区分软件的功能范围。通常,软件被打包成一个框架,或者多个紧密关联的框架(如Foundation和Application Kit框架)。前缀可以防止第三方开发者与Apple之间符号的命名冲突(也可以防止Apple内部不同框架之间的冲突)。
- 前缀有规定的格式。它由两到三个大写字母组成,不能使用下划线和子前缀。如下所示:
前缀 | Cocoa框架 |
---|---|
NS | Foundation |
NS | Application Kit |
AB | Address Book |
IB | Interface Builder |
- 命名类,协议,函数,常量和结构体时使用前缀。命名成员方法时不使用前缀,因为方法已经在定义它的类的命名空间中。同样的,不使用前缀命名结构体的字段。
1.3 书写规范
为API元素命名时,遵循以下简单的书写规范:
- 对于多个单词组成的名称,不要使用标点符号作为名称的一部分或者分隔符(下划线,破折号等等);相反,应该大写每个单词的首字母,并将单词联系拼写(例如,runTheWorldsTogether)——即驼峰式命名。请注意以下限制:
- 方法名的首字母小写,之后每个单词的首字母大写。不要使用前缀。
fileExistsAtPath:isDirectory:
该规则的一个例外是方法名以一个广为人知的缩写开头,例如:TIFFRepresentation (NSImage)。
- 函数名和常量名使用与其关联类相同的前缀,并大写每个单词的首字母。
NSRunAlertPanel
NSCellDisabled
- 避免在方法名中使用下划线作为前缀来表示私有方法(可以在实例变量名中使用下划线作为前缀)。Apple保留了该规范的使用。如果第三方这样使用,可能会导致命名空间冲突;他们可能无意的用自己的方法覆盖了已经存在的私有方法,这会导致严重的后果。请参考“私有方法”一节。
1.4 类和协议的名字
类名应该包含一个明确描述类(或类的对象)是什么或者做什么的名词。类名要有一个合适的前缀(参考“前缀”一节)。Foundation和Application Kit框架很多这样的例子,例如NSString,NSDate,NSScanner,NSApplication,UIApplication,NSButton,UIButton。
协议应该根据如何分组行为来命名:
- 大部分协议组合相关的方法,而不关联任何具体的类。这种类型的协议名称不要与类名混淆。通常使用动名词("...ing")的格式:
代码 | 点评 |
---|---|
NSLocking | 好 |
NSLock | 不好(像类名) |
- 有些协议组合一些不相关的方法(而不是创建多个独立的小协议)。这些协议倾向于与某个类关联,该类是协议的主要体现者。这种情况下,协议名称与类名一样。
NSObject协议就是这样一个例子。该协议组合不相关的方法,包括查询对象在类继承关系中位置的方法,调用特殊方法的方法,增加或减少引用计数的方法。因为NSObject类是这些方法的主要体现者,所以用类名命名这个协议。
1.5 头文件
如何命名头文件很重要,因为使用的命名规范表明了头文件的内容。
- 声明独立的协议或类。如果类或协议不是分组的一部分,把它的声明放在单独的文件中,名称与类或协议相同。
头文件 | 声明 |
---|---|
NSLocale.h | NSLocale类 |
- 声明相关的类和协议。对于一组相关的声明(类,类别和协议),把声明放在一个文件中,文件名为主要的类,类别或协议。
头文件 | 声明 |
---|---|
NSString.h | NSString和NSMutableString类 |
NSLock.h | NSLocking协议和NSLock,NSConditionLock,NSRecursiveLock类 |
包括框架头文件。每个框架应该有一个与框架同名的头文件,其中包括该框架所有公开的头文件。
添加API到另一个框架中的类。如果在框架中声明的方法是另一个框架中某个类的类别,在原类名后添加“Additions”;例如Application Kit中的NSBundleAdditions.h头文件。
相关的函数和数据类型。讲相关的函数,常量,结构体,以及其它数据类型放在同一个头文件中,并以合适的名字命名,例如NSGraphics.h(Application Kit)。
2 方法命名
方法可能是编程接口中最常见的元素,因此要非常小心的为它们命名。本节讨论方法命名的以下几个方法:
2.1 一般性规则
为方法命名时,记住以下一般性指南:
第一个单词的首字母小写,之后的每个单词的首字母大写。不要使用前缀。请参考“书写规范”。
有两种例外情况:第一,使用广为人知的大写字母缩写命名方法(例如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 |
2.2 访问方法
访问方法是对象属性的读取和设置方法,其命名的特定推荐格式依赖于如何描述属性:
- 如果用名词描述属性,格式为:
- (type)noun;
- (void)setNoun:(type)aNoun;
例如:
- (NSString *)title;
- (void)setTitle:(NSString *)aTitle;
- 如果用形容词描述属性,格式为:
- (BOOL)isAdjective;
- (void)setAdjective:(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; | 错误 |
- 可以使用情态动词(can,should,will等)明确意图,但不要使用”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作为in/out参数,表示调用者不需要一个或多个返回值。
2.3 代理方法
特定事件发生时,对象在其代理中(如果代理实现了代理方法)调用代理方法。它们有独特的格式,同样也适用于对象的数据源方法。
- 以发送消息的对象的类名开头,省略类的前缀,并小写第一个字母:
- (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”。
- (void)browserDidScroll:(NSBrowser *)sender;
- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window;
- 询问代理对象能否执行某个操作可以使用“did”或“will”,但最好使用“should”。
- (BOOL)windowShouldClose:(id)sender;
2.4 集合方法
管理对象集合的对象(集合中的对象叫做元素)方法具备如下的格式:
- (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;
集合方法的实现需要考虑以下细节:
这些方法通常拥有插入对象,所以添加或插入的代码需要retain元素,并在移除的代码中release元素。
如果被插入的对象需要主对象的指针时,通常使用set...方法设置该指针,并且不要retain。在insertLayoutManager:atIndex:方法中,NSLayoutManager类使用如下方法:
- (void)setTextStorage:(NSTextStorage *)textStorage;
- (NSTextStorage *)textStorage;
通常不会直接调用setTextStorage:方法,而是覆写它。
另一个关于上面集合方法规范的例子是NSWindow类:
- (void)addChildWindow:(NSWindow *)childWin ordered:(NSWindowOrderingMode)place;
- (void)removeChildWindow:(NSWindow *)childWin;
- (NSArray *)childWindows;
- (NSWindow *)parentWindow;
- (void)setParentWindow:(NSWindow *)window;
2.5 方法参数
命名方法参数需要考虑以下一般性规则:
与方法名一样,参数名的第一个单词的首字母小写,之后的每个单词的首字母大写(例如:removeObject:(id)anObject)。
不要在名称中使用pointer或ptr。用参数的类型,而不是名称声明是否是指针。
避免使用one-,two-letter作为参数名。
避免少写几个字符而使用缩写。
按惯例(在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
...keyEquivalent:(NSString *)charCode
...length:(int)numBytes
...point:(NSPoint)aPoint
...stringValue:(NSString *)aString
...tag:(int)anInt
...target:(id)anObject
...title:(NSString *)aString
2.6 私有方法
大多数情况下,私有方法的命名与公共方法命名的规范相同。但通常给私有方法加一个前缀,以便与公共方法区分开来。即使在这个规范下,私有方法名还是会导致奇怪的问题。设计一个从Cocoa框架继承的子类时,不知道自己的私有方法是否无意的覆盖了框架中同名的私有方法。
Cocoa框架中大部分私有方法名使用下划线(例如:_fooData)标志为私有的。因此遵循下面两条建议:
不要使用下划线作为私有方法的前缀,Apple保留了这个规范。
如果继承Cocoa框架中的一个超大类(例如:NSView或UIView),并想要完全区分父类私有方法和自己的私有方法,可以为私有方法添加自己的前缀。这个前缀应该尽可能唯一,例如基于公司或工程的名称,如“XX_”这样的格式。如果工程名是“Byte Flogger”,前缀可以是“BF_addObject:”。
尽管为私有方法添加前缀的建议与之前为方法命名的规范自相矛盾,但这里的目的不一样:防止无意覆写父类中的私有方法。
3 函数命名
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)
- 如果返回值是boolean类型,函数用判断动词开头。
BOOL NSDecimalIsNotANumber(const NSDecimal *decimal)
4 属性和数据类型命名
本节描述声明属性,实例变量,常量,通知和异常的命名规范。
4.1 声明属性和实例变量
一个声明的属性实际上声明了属性的一个访问方法,因此属性的命名规范大体上与访问方法相同(参考“访问方法”)。如果属性用名称或动词描述,格式为:
@property (…) type nounOrVerb;
例如:
@property (strong) NSString *title;
@property (assign) BOOL showsAlpha;
如果属性用形容词描述,省略“is”前缀,但指明get访问器的规范的名称,例如:
@property (assign, getter=isEditable) BOOL editable;
多数情况下,使用一个声明的属性时,同时生成了一个相应的实例变量。
确保实例变量简明扼要的描述了存储的属性。通常不直接访问实例变量,而是使用访问方法(在init和dealloc方法中直接访问实例变量)。为了达到这个目的,在实例变量名之前添加下划线作为前置,例如:
@implementation MyClass {
BOOL _showsTitle;
}
如果使用一个生命的属性生成实例变量,在@synthesize中指定实例变量的名称。
@implementation MyClass
@synthesize showsTitle=_showsTitle;
为类添加实例变量时需要考虑以下几个方面:
避免显式的声明public实例变量。
开发者应该关注对象的接口,而不是如何存储数据的细节。通过声明属性和生成相应的实例变量避免显式的声明实例变量。如果需要声明实例变量,用@private或@protected显式的声明。
如果希望类被继承,并且子类需要直接访问数据,使用@protected。如果实例变量是类实例的可访问属性,确保使用访问方法(尽可能使用声明的属性)。
4.2 常量
常量命名规则根据创建方式的不同而大不相同。
4.2.1 枚举常量
使用枚举定义一组相关的整数常量。
枚举常量与其typedef命名遵循函数的命名规范(参考“函数命名”)。
下面的例子来自NSMatrix.h。本例中typedef标识(_NSMatrixMode)不是必须的。
typedef enum _NSMatrixMode {
NSRadioModeMatrix = 0,
NSHighlightModeMatrix = 1,
NSListModeMatrix = 2,
NSTrackModeMatrix = 3
} NSMatrixMode;
- 可以创建不具名枚举,比如位掩码。
enum {
NSBorderlessWindowMask = 0,
NSTitledWindowMask = 1 << 0,
NSClosableWindowMask = 1 << 1,
NSMiniaturizableWindowMask = 1 << 2,
NSResizableWindowMask = 1 << 3
};
4.2.2 使用const创建的常量
使用const创建浮点常量。如果常量与其它常量不相关,可以使用const创建整数常量;否则使用枚举。
const常量格式如下面的声明所示:
const float NSLightGray;
枚举常量的命名规范跟函数命名规范相同。
4.2.3 其它类型的常量
通常不适用#define预处理命令创建常量。对于整数常量,使用枚举,浮点数常量使用const。
使用大写字母定义预处理宏,来决定代码块是否执行。例如:
#ifdef DEBUG
- 编译器定义的宏的头尾都有双下划线,例如:
__MACH__
- 定义常量字符串用作通知名和字典的键。使用常量字符串,编译器可以执行拼写检查。Cocoa框架提供了很多常量字符串的例子:
APPKIT_EXTERN NSString *NSPrintCopies;
字符串在实现文件中赋值。(注意:APPKIT_EXTERN宏在Objective-C中等价于extern)
4.3 通知和异常
通知和异常的命名遵循相似的规则,但它们有各自的推荐使用模式。
4.3.1 通知
如果一个类有代理,那它的大部分通知可能由代理类的代理方法接收。这些通知的名称应该能够反应对应的代理方法。例如,当应用程序提交NSApplicationDidBecomeActiveNotification通知时,全局的NSApplication对象的代理自动注册接收applicationDidBecomeActive:消息。
通知由如下形式的全局NSString对象标识:
[Name of associated class] + [Did | Will] + [UniquePartOfName] + Notification
例如:
NSApplicationDidBecomeActiveNotification
NSWindowDidMiniaturizeNotification
NSTextViewDidChangeSelectionNotification
NSColorPanelColorDidChangeNotification
4.3.2 异常
虽然可以因为任何目的而使用异常(由NSException类和相关函数实现),但Cocoa使用异常来处理类似数组越界的编程错误。Cocoa不适用异常处理常规的,可预料的错误。这些情况下,使用nil,NULL,NO,或错误代码之类的返回值。
异常由如下形式的全局NSString对象标识:
[Prefix] + [UniquePartOfName] + Exception
名称的唯一部分(UniquePartOfName)部分由单词组成,每个单词的第一个字母大写。例如:
NSColorListIOException
NSColorListNotEditableException
NSDraggingException
NSFontUnavailableException
NSIllegalSelectorException
5 可接受的缩写和首字母缩写
设计编程接口时,通常不使用缩写(参考“一般性规则”)。然后下面列出的缩写要么是相沿成习,要么是过去广泛使用的,所以可以继续使用。关于缩写有一些额外的注意事项:
标准C库中长期使用的缩写形式是可以接受的,例如:“alloc”,“getc”。
在参数名中可以更*的使用缩写,例如:“imageRep”,“col”(“column”),“obj”,“otherWin”。
缩写 | 含义和注释 |
---|---|
alloc | Allocate |
app | Application。例如,全局application对象NSApp。但在代理方法,通知等中,应该使用“application”全拼。 |
alt | Alternate |
calc | Calculate |
dealloc | Deallocate |
func | Function |
horiz | Horizontal |
info | Information |
init | Initialize |
int | Integer |
max | Maximum |
min | Minimum |
msg | Message |
nib | Interface Builder文件 |
pboard | Pasteboard(只在常量中) |
rect | Rectangle |
Rep | Representation(在类名中使用,如NSBitmapImageRep) |
temp | Temporary |
vert | Vertical |
可以使用计算机行业的常见缩写和首字母缩写,例如:
ASCII
PDF
XML
HTML
URL
RTF
HTTP
TIFF
JPG
PNG
GIF
LZW
ROM
RGB
CMYK
MIDI
FTP
6 框架开发者的小贴士和技术
没有开发框架,不敢随便翻译,以后有机会再翻译。
上一篇: 3 Steps
下一篇: 面向协议编程与 Cocoa 的邂逅(上)