首页

时间和精神的房子
壹只iOS程序员的修行世界,欢迎来访

如果文章对您有所帮助
将是我最大的荣幸

Cocoa代码风格指南之命名规范(一)

本文基于 GoogleApple 的代码风格指南中关于命名规范部分的总结。Apple 的指南基本都是命名规范,而 Google 的指南则的比较丰富。命名、风格、使用都有涉及,但讲的都是比较核心的规范。

注:本文只针对 Objective-C。如果是 Swift ,你还需要再看下官方文档的建议。

  • :Apple 明确给出建议
  • :Google 明确给出建议
  • :通用做法(主观)

通用规则

  • 通过命名可清晰的看出方法及其参数的作用。避免歧义。并在此基础上尽量做到简短。
insertObject:atIndex: // 正确
insert:at: // 错误
  • 即便命名很长也不要使用简写。除了本文最后那些历史悠久的简写。
destinationSelection // 正确
destSel // 错误
  • 尽可能使用与 Cocoa 编程接口的命名保持统一。在不同类中实现相同功能的方法应该具有相同的名称。
- (int)tag // 在 UIView, UIControl 中都具有相同的功能
- (void)setStringValue:(NSString *) // 在许多 Cocoa classes 中都具有相同的功能
  • 所有方法/常量/变量/属性都应使用"小驼峰"命名法。除了本文最后那些广为人知的大写字母缩略词。

  • 所有类/类别/协议使用“大驼峰”命名法。

  • 类名应包含一个明确描述该类(或类的对象)是什么或做什么的名词。

  • 在为类添加实例变量是要注意:

  1. 避免创建 public 实例变量。
  2. 使用 @private,@protected 显式限定实例变量的访问权限。
  3. 确保实例变量名简明扼要地描述了它所代表的属性。

协议

  • 大多数协议仅组合一组相关的方法,而不关联任何类,这种协议的命名应该使用动名词(ing),以不与类名混淆。
NSLocking // 正确
NSLock // 错误
  • 有些协议组合一些彼此无关的方法(这样做是避免创建多个独立的小协议)。这样的协议倾向于与某个类关联在一起,该类是协议的主要体现者。在这种情形,我们约定协议的名称与该类同名。NSObject 协议就是这样一个例子。这个协议组合一组彼此无关的方法,有用于查询对象在其类层次中位置的方法,有使之能调用特殊方法的方法以及用于增减引用计数的方法。由于 NSObject 是这些方法的主要体现者,所以 Apple 用类的名称命名这个协议。

方法

  • 参数前面的单词要能描述该参数。而且方法名应尽量读起来就像句子,所以应该选择与方法名连在一起读起来通顺的参数名。
- (id)viewWithTag:(int)aTag; // 正确
- (id)taggedView:(int)aTag; // 错误
  • 表示对象行为的方法,名称以动词开头。
- (void)invokeWithTarget:(id)target:
- (void)selectTabViewItem:(NSTableViewItem *)tableViewItem
  • 方法名称中不要出现 do 或 does,因为这些助动词没什么实际意义。也不要在动词前使用副词或形容词修饰。

  • 如果方法返回方法接收者的某个属性,直接用属性名称命名。不要使用 get。

- (CGSize)cellSize; // 正确
- (CGSize)getCellSize; // 错误
  • 只有在方法需要间接返回多个值的情况下,才使用 get。
- (void)getLineDash:(float *)pattern count:(int *)count phase:(float *)phase; UIBezierPath
  • 细化基类中的已有方法:创建一个新方法,其名称是在被细化方法名称后面追加参数关键词。
- (instancetype)initWithFrame:(NSRect)frameRect; // UIView
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style; // UITableView, UIView 的子类
  • 不要使用 and 来连接用属性作参数关键字。
- (int)runModalForDirectory:(NSString *)path file:(NSString *)name types:(NSArray *)fileTypes;
- (int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes;
  • 如果方法描述两种独立的行为,使用 and 来串接它们。
- (BOOL) openFile:(NSString *)fullPath withApplication:(NSString *)appName andDeactivate:(BOOL)flag; NSWorkspace 
  • 不要在方法的参数名中使用 pointer 或 ptr,让参数的类型来说明它是指针。

  • 避免使用 one, two,...,作为参数名。

  • 按照 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

Delegate & DataSource

  • 委托方法是那些在特定事件发生时可被对象调用,并声明在对象的委托类中的方法。它们有独特的命名约定,这些命名约定同样也适用于对象的数据源方法。

1.名称以标示发送消息的对象的类名开头,省略类名的前缀并小写类第一个字符。

- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row;
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename;

2.冒号紧跟在类名之后(随后的那个参数表示委派的对象)。该规则不适用于只有一个 sender 参数的方法。

- (BOOL)applicationOpenUntitledFile:(NSApplication *)sender;

3.上面的那条规则也不适用于响应通知的方法。在这种情况下,方法的唯一参数表示通知对象。

- (void)windowDidChangeScreen:(NSNotification *)notification;

4.用于通知委托对象操作即将发生或已经发生的方法名中要使用 did 或 will。

- (void)browserDidScroll:(NSBrowser *)sender;
- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window;

5.用于询问委托对象可否执行某操作的方法名中可使用 did 或 will,但最好使用 should。

- (BOOL)windowShouldClose:(id)sender;

C形式的函数

  • 函数命名与方法命名相似,但有几点不同:

  1. 它们有前缀,其前缀与你使用的类和常量的前缀相同。
  2. 大写前缀后紧跟的第一个单词首字符。

  • 大多数函数名称以动词开头,这个动词描述该函数的行为。
NSHighlightRect
NSDeallocateObject
  • 查询属性的函数有更多的规则要遵循:

  1. 查询第一个参数的属性的函数,省略动词。
unsigned int NSEventMaskFromType(NSEventType type)
float NSHeight(NSRect rect)
  1. 返回值为引用的方法,使用 get。
const char *NSGetSizeAndAlignment(const char *typePtr, unsigned int *sizep, unsigned int *alignp)
  1. 返回布尔值的函数,名称使用判断动词 is/does 开头。
BOOL NSDecimalIsNotANumber(const NSDecimal *decimal)

存取方法

  • 存取方法是指用来获取和设置类属性值的方法,属性的不同类型,对应着不同的存取方法规范。

  • 如果属性是用名词描述的,则命名格式为:

- (NSString *)title;
- (void)setTitle:(NSString *)title;
  • 如果属性是用形容词描述的,则命名格式为:
- (BOOL)isEditable;
- (void)setEditable:(BOOL)editable;
  • 如果属性是用动词描述的,则命名格式为:(动词要用现在时时态)
- (BOOL)showsAlpha;
- (void)setShowsAlpha:(BOOL)showsAlpha;
  • 不要使用动词的过去分词形式作形容词使用。
- (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; // 正确
- (void)shouldCloseDocument; // 正确
- (void)setDoseAcceptGlyphInfo:(BOOL)flag; // 错误
- (BOOL)doseAcceptGlyphInfo; // 错误

常量

  • 常量命名规则根据常量创建的方式不同而大为不同。

  • 枚举常量

1.使用枚举来定义一组相关的整数常量。
2.枚举常量与其 typedef 命名遵守函数命名规则。如:来自 NSMatrix.h 中的例子:(本例中的 typedef tag(_NSMatrixMode)不是必须的)

typedef enum _NSMatrixMode {
    NSRadioModeMatrix           = 0,
    NSHighlightModeMatrix       = 1;
    NSListModeMatrix           = 2,
    NSTrackModeMatrix           = 3
} NSMatrixMode;

3.位掩码常量可以使用不具名枚举。如:

enum {
    NSBorderlessWindowMask         = 0,
    NSTitledWindowMask             = 1 << 0,
    NSClosableWindowMask           = 1 << 1,
    NSMiniaturizableWindowMask      = 1 << 2,
    NSResizableWindowMask          = 1 << 3
};
  • 使用 typedef NS_ENUM/NS_OPTIONS 来声明枚举。

C++11 标准扩充了枚举的特性,可以指明用何种“底层数据类型”来保存枚举类型的变量。这样做的好处是可以向前声明枚举变量。Foundation 框架中定义了一些辅助宏来指定用于保存枚举值的底层数据类型。这些宏具备向后兼容能力,如果目标平台的编译器支持新的标准,就使用新语法,否则改用旧式语法。所以建议这样来使用。

typedef NS_ENUM(NSInteger, UIViewAnimationCurve) {
    UIViewAnimationCurveEaseInOut,         // slow at beginning and end
    UIViewAnimationCurveEaseIn,            // slow at beginning
    UIViewAnimationCurveEaseOut,           // slow at end
    UIViewAnimationCurveLinear
};
  • const 常量

  1. 使用 const 来修饰浮点常量或彼此没有关联的整数常量。
  2. 枚举常量命名规则与函数命名规则相同。const 常量命名范例:
const float NSLightGray;
  • 其他常量

  1. 通常不使用 #define 来创建常量。如上面所述,整数常量请使用枚举,浮点数常量请使用 const。
  2. 使用大写字母来定义预处理编译宏。如:#ifdef DEBUG。
  3. 编译器定义的宏名首尾都有双下划线。如:__MACH__。
  4. 为 notification 名及 dictionary key 定义字符串常量,从而能够利用编译器的拼写检查,减少书写错误。Cocoa 框架提供了很多这样的范例:
APPKIT_EXTERN NSString *NSPrintCopies;

实际的字符串值在实现文件中赋予。(注意:APPKIT_EXTERN 宏等价于 Objective-C 中 extern)

数据集合

  • 管理对象(集合中的对象被称之为元素)的集合类,约定要具备如下形式的方法:
- (void)addLayoutManager:(NSLayoutManager *)adObj;
- (void)removeLayoutManager:(NSLayoutManager *)anObj;
- (NSArray *)layoutManagers;
  • 集合方法命名有如下一些限制和约定:

  1. 如果集合中的元素无序,返回 NSSet,而不是 NSArray。
  2. 如果将元素插入指定位置的功能很重要,则需具备如下方法:
- (void)insertElement:(elementType)anObj atIndex:(int)index;
- (void)removeElementAtIndex:(int)index;
  • 集合方法的实现要考虑如下细节:

1.以上集合类方法通常负责管理元素的所有者关系,在 add 或 insert 的实现代码里会 retain 元素,在 remove 的实现代码中会 release 元素。
2.当被插入的对象需要持有指向集合对象的指针时,通常使用 set... 来命名其设置该指针的方法,且不要 retain 集合对象。比如上面的 insertLayerManager:atIndex: 这种情形,NSLayoutManager 类使用如下方法:

- (void) setTextStorage:(NSTextStorage *)textStorage;
- (NSTextStorage *)textStorage;

3.通常你不会直接调用 setTextStorage:,而是覆写它。

  • 另一个关于集合约定的例子来自 NSWindow 类:
- (void)addChildWindow:(NSWindow *)childWin ordered:(NSWindowOrderingMode)place;
- (void)removeChildWindow:(NSWindow *)childWin;
- (NSArray *)childWindows;
- (NSWindow *)parentWindow;
- (void)setParentWindow:(NSWindow *)window;

前缀

  • 类名使用两到三个字母的前缀

Objective-C 没有其他语言那种内置的命名空间机制。所以,在命名类和协议时应使用前缀。Apple宣称其保留使用所有“两字母前缀”的权利。所以我们理论上最好选用三个字母作为前缀。然而,大部分的项目都使用两字母前缀 ,甚至像 AFNetworking 和 SDWebImage。所以,二到三字母的前缀都是可以的。苹果公司的私有类会在前缀前加单下划线,用户的私有类不需要也不能用这样的前缀作为标识符。Google 对前缀的建议是:应用层的代码,应该尽量避免不必要的前缀(如果项目拥有许多独立的业务模块,应用层也可以用前缀来当做命名空间来避免命名冲突)。为每个类都添加相同的前缀无助于可读性。当编写的代码期望在不同应用程序间复用时,才应使用前缀。例如,Coding 的源码就没有加前缀,而那些开源库的代码基本都有前缀。

  • 给私有变量加下划线前缀

我们使用属性时Xcode会自动为我们在实现文件中合成以下划线开头的成员变量。所以在我们直接声明不公开的成员变量时也应该以下划线开头。

  • 给私有方法加前缀

私有方法应该使用前缀进行标示来避免子类命名冲突。苹果公司喜欢用单下划线作为私有方法的前缀。所以其在官方文档中强调,开发者不应该用单下划线作为前缀。这里《Effective Objective-C 2.0》的作者 Matt Galloway的习惯是使用"p_"作为前缀。自己用过"__"和"m_",后来又改为"p_"。然而倔强的 YYKit 就是要用单下划线作为前缀。如果你有时间像大神一样去扫描项目中的 API 并确保没有对系统 API 产生影响,你也可以这样用,毕竟这是最便捷美观的方式。也许你已经受到大神的感召也准备这样使用(比如我),那只要你命名时别使用那些常用的词汇即可。比如,如果你想重置 viewController 而使用"_resetViewController"的私有方法名,那么恭喜你命中了系统的私有 API。苹果公司建议私有方法的前缀具有唯一性即可,如基于你公司的名称,或工程的名称,并以“XX_”形式给出。比如你的工程名为"Byte Flogger",那么就可以是“BF_addObject:”。但这样太丑了。

  • 常量名(如宏定义、枚举、静态局部变量等)应该以小写字母 k 开头,如:kInvalidHandle,kWritePerm。

  • 枚举中的常量应使用与枚举的名称作为前缀。

typedef NS_ENUM(NSInteger, UIViewAnimationCurve) {
    UIViewAnimationCurveEaseInOut,         // slow at beginning and end
    UIViewAnimationCurveEaseIn,            // slow at beginning
    UIViewAnimationCurveEaseOut,           // slow at end
    UIViewAnimationCurveLinear
};

头文件

  • 声明孤立的类或协议:将孤立的类或协议声明放置在单独的头文件中,该头文件名称与类或协议同名。例如:头文件 NSApplication.h 声明 NSApplication 类。

  • 声明相关联的类或协议:将相关联的声明(类,类别及协议) 放置在一个头文件中,该头文件名称与主要的类/类别/协议的名字相同。例如:

头文件 NSString.h 声明 NSString 和 NSMutableString 类
头文件 NSLock.h 声明 NSLocking 协议和 NSLock, NSConditionLock, NSRecursiveLock 类

  • 包含框架头文件:每个框架应该包含一个与框架同名的头文件,该头文件包含该框架所有公开的头文件。例如:头文件 Foundation.h 包含框架 Foundation.framework 的所有头文件。

  • 为已有框架中的某个类扩展 API:如果要在一个框架中声明属于另一个框架某个类的范畴类的方法,该头文件的命名形式为:原类名+“Additions”。例如:Application Kit 中的 NSBundleAdditions.h。

  • 相关联的函数与数据类型:将相联的函数,常量,结构体以及其他数据类型放置到一个头文件中,并以合适的名字命名。如 Application Kit 中的 NSGraphics.h。

异常和通知

  • 异常

虽然你可以处于任何目的而使用异常(由 NSException 类及相关类实现),Cocoa 通常不使用异常来处理常规的,可预料的错误。在这些情形下,使用诸如 nil, NULL, NO或错误代码之类的返回值。异常的典型应用类似数组越界之类的编程错误。异常由具有如下形式的全局 NSString 对象标识:

[Prefix] + UniquePartOfName + Exception

UniquePartOfName 部分是有连续的首字符大写的单词组成。例如:

NSColorListIOException
NSColorListNotEditableException
NSDraggingException
NSFontUnavailableException
NSIllegalSelectorException
  • 通知

如果一个类有委托,那它的大部分通知可能由其委托的委托方法来处理。这些通知的名称应该能够反应其响应的委托方法。比如,当应用程序提交如下通知时,全局 NSApplication 对象的委托会注册从而能够接收 applicaitonDidBecomeActive: 消息。

NSApplicationDidBecomeActiveNotification

通知由具有如下形式的全局 NSString 对象标识:

[相关联类的名称] + [Did 或 Will] + [UniquePartOfName] + Notification

例如:

NSApplicationDidBecomeActiveNotification
NSWindowDidMiniaturizeNotification
NSTextViewDidChangeSelectionNotification
NSColorPanelColorDidChangeNotification

附录

  • 可接受的缩略语

在设计编程接口时,通常名称不要缩写。然而,下面列出的缩写要么是固定下来的要么是过去被广泛使用的,所以你可以继续使用。关于缩写有一些额外的注意事项:

  1. 标准 C 库中长期使用的缩写形式是可以接受的。如:"alloc","getc"。
  2. 你可以在参数名中更自由地使用缩写。如:imageRep,col(column),obj,otherWin。
    alloc       Allocate
    msg         Message
    alt         Alternate
    nib         Interface Builder archive
    app         Application
    pboard      Pasteboard
    calc        Calculate
    rect        Rectangle
    dealloc     Deallocate
    Rep         Representation
    func        Function
    temp        Temporary
    horiz       Horizontal
    vert        Vertical
    info        Information
    init        Initialize
    max         Maximum
    
    ASCII,PDF,XML,HTML,URL,RTF,HTTP,TIFF
    JPG,GIF,LZW,ROM,RGB,CMYK,MIDI,FTP
```xuyafei.com

关注作者

分享本文

目录