欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Cocoa编码指南

程序员文章站 2024-01-15 08:11:52
...

翻译自“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 前缀

前缀是名称的重要组成部分。它们可以区分软件的功能范围。通常,软件被打包成一个框架,或者多个紧密关联的框架(如FoundationApplication 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 类和协议的名字

类名应该包含一个明确描述类(或类的对象)是什么或者做什么的名词。类名要有一个合适的前缀(参考“前缀”一节)。FoundationApplication Kit框架很多这样的例子,例如NSStringNSDateNSScannerNSApplicationUIApplicationNSButtonUIButton

协议应该根据如何分组行为来命名:

  • 大部分协议组合相关的方法,而不关联任何具体的类。这种类型的协议名称不要与类名混淆。通常使用动名词("...ing")的格式:
代码 点评
NSLocking
NSLock 不好(像类名)
  • 有些协议组合一些不相关的方法(而不是创建多个独立的小协议)。这些协议倾向于与某个类关联,该类是协议的主要体现者。这种情况下,协议名称与类名一样。

NSObject协议就是这样一个例子。该协议组合不相关的方法,包括查询对象在类继承关系中位置的方法,调用特殊方法的方法,增加或减少引用计数的方法。因为NSObject类是这些方法的主要体现者,所以用类名命名这个协议。

1.5 头文件

如何命名头文件很重要,因为使用的命名规范表明了头文件的内容。

  • 声明独立的协议或类。如果类或协议不是分组的一部分,把它的声明放在单独的文件中,名称与类或协议相同。
头文件 声明
NSLocale.h NSLocale
  • 声明相关的类和协议。对于一组相关的声明(类,类别和协议),把声明放在一个文件中,文件名为主要的类,类别或协议。
头文件 声明
NSString.h NSStringNSMutableString
NSLock.h NSLocking协议和NSLockNSConditionLockNSRecursiveLock
  • 包括框架头文件。每个框架应该有一个与框架同名的头文件,其中包括该框架所有公开的头文件。

  • 添加API到另一个框架中的类。如果在框架中声明的方法是另一个框架中某个类的类别,在原类名后添加“Additions”;例如Application Kit中的NSBundleAdditions.h头文件。

  • 相关的函数和数据类型。讲相关的函数,常量,结构体,以及其它数据类型放在同一个头文件中,并以合适的名字命名,例如NSGraphics.hApplication 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框架中的一个超大类(例如:NSViewUIView),并想要完全区分父类私有方法和自己的私有方法,可以为私有方法添加自己的前缀。这个前缀应该尽可能唯一,例如基于公司或工程的名称,如“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 框架开发者的小贴士和技术

没有开发框架,不敢随便翻译,以后有机会再翻译。