清晰性
代码 | 点评 |
---|---|
insertObject: atIndex: | :material-check: 好 |
insert: at: | 不清晰:要插入什么?“at”表示什么? |
removeObjectAtIndex: | 好 |
removeObject: | 这样也不错,因为方法是移除作为参数的对象 |
remove: | 不清晰:要移除什么? |
代码 | 点评 |
---|---|
destinationSelection: | :material-check: 好 |
destSel: | :material-close: 不好 |
setBackgroundColor: | :material-check: 好 |
setBkgdColor: | :material-close: 不好 |
你可能会认为某个缩写广为人知,但有可能并非如此,尤其是当你的代码被来自不同文化和语言背景的开发人员所使用时。
然而你可以使用少数非常常见,历史悠久的缩写。请参考[可接受的缩略名]()一节
避免使用有歧义的 API 名称,如那些能被理解成多种意思的方法名称
代码 | 点评 |
---|---|
sendPort: | 是发送端口还是返回一个发送端口? |
displayName: | 是显示一个名称还是返回用户界面中控件的标题? |
一致性
代码 | 点评 |
---|---|
- (int) tag | 在 NSView,NSCell,NSControl 中有定义 |
- (void)setStringValue:(NSString *) | 在许多Cocoa类中有定义 |
更多请看[方法参数]()
通常,软件会被打包成一个框架或多个紧密相关的框架(如 Foundation 和 Application Kit 框架)。但由于Cocoa没有像C++一样的命名空间概念,所以我们只能用前缀来区分软件的功能范畴,防止命名冲突。
下面是常见的苹果官方的前缀
前缀 | Cocoa 框架 |
---|---|
NS | Foundation |
NS | Application Kit |
AB | Address Book |
IB | Interface Builder |
比如在我们的APP中蓝牙模块(Bluetooth low energy)管理类
@interface BLEManager : NSObject
@property (assign) BLEDeviceType deviceType;
@end
在为 API 元素命名时,请遵循如下一些简单的书写约定
对于包含多个单词的名称,不要使用标点符号作为名称的一部分或作为分隔符(下划线,破折号等); 此外,大写每个单词的首字符并将这些单词连续拼写在一起。请注意以下限制:
类名应包含一个明确描述该类(或类的对象)是什么或做什么的名词。类名要有合适的前缀(请参考前缀 一节)。Foundation 及 Application Kit 有很多这样例子,如:NSString, NSData, NSScanner, NSApplication, NSButton 以及 UIButton。
协议应该根据对方法的行为分组方式来命名。
代码 | 点评 |
---|---|
NSLocking | good |
NSLock | 糟糕,它看起来像类名 |
头文件的命名方式很重要,我们可以根据其命名知晓头文件的内容。
头文件 | 声明 |
---|---|
NSApplication.h | NSApplication 类 |
头文件 | 声明 |
---|---|
NSString.h | NSString 和 NSMutableString 类 |
NSLock.h | NSLocking 协议和 NSLock, NSConditionLock, NSRecursiveLock 类 |
头文件 | 声明 |
---|---|
Foundation.h | Foundation.framework |
为方法命名时,请考虑如下一些一般性规则:
new
作为前缀。表示对象行为的方法,名称以动词开头:
- (void) invokeWithTarget:(id)target:
- (void) selectTabViewItem:(NSTableViewItem *)tableViewItem
名称中不要出现 do或does,因为这些助动词没什么实际意义。也不要在动词前使用副词或形容词修饰。
代码 | 点评 |
---|---|
- (NSSize) cellSize; | 对 |
- (NSSize) calcCellSize; | 错 |
- (NSSize) getCellSize; | 错 |
代码 | 点评 |
---|---|
- (void) sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag; | 对 |
- (void) sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag; | 错 |
代码 | 点评 |
---|---|
- (id) viewWithTag:(int)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, a subclass of NSView
不要使用 and 来连接用属性作参数的关键字
代码 | 点评 |
---|---|
- (int)runModalForDirectory:(NSString)path file:(NSString)name types:(NSArray *)fileTypes; | 对 |
- (int)runModalForDirectory:(NSString)path andFile:(NSString)name andTypes:(NSArray *)fileTypes; | 错 |
虽然上面的例子中使用 add 看起来也不错,但当你方法有太多参数关键字时就有问题。
如果方法描述两种独立的行为,使用 and 来串接它们
- (BOOL) openFile:(NSString *)fullPath withApplication:(NSString NSWorkspace *)appName andDeactivate:(BOOL)flag;//NSWorkspace.
访问方法是对象属性的读取与设置方法。其命名有特定的格式依赖于属性的描述内容。
如果属性是用名词描述的,则命名格式为:
- (void) setNoun:(type)aNoun;
- (type) noun;
例如:
- (void) setgColor:(NSColor *)aColor;
- (NSColor *) color;
如果属性是用形容词描述的,则命名格式为:
- (void) setAdjective:(BOOL)flag;
- (BOOL) isAdjective;
例如:
- (void) setEditable:(BOOL)flag;
- (BOOL) isEditable;
如果属性是用动词描述的,则命名格式为:(动词要用现在时时态)
- (void) setVerbObject:(BOOL)flag;
- (BOOL) verbObject;
例如:
- (void) setShowAlpha:(BOOL)flag;
- (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; //错
只有在方法需要间接返回多个值的情况下,才使用 get
- (void) getLineDash:(float *)pattern count:(int *)count phase:(float *)phase; //NSBezierPath
像上面这样的方法,在其实现里应允许接受 NULL 作为其 in-out 参数,以表示调用者对一个或多个返回值不感兴趣。
委托方法是那些在特定事件发生时可被对象调用,并声明在对象的委托类中的方法。它们有独特的命名约定,这些命名约定同样也适用于对象的数据源方法。
名称以标示发送消息的对象的类名开头,省略类名的前缀并小写类第一个字符
- (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;
管理对象(集合中的对象被称之为元素)的集合类,约定要具备如下形式的方法:
- (void)addElement:(elementType)adObj;
- (void)removeElement:(elementType)anObj;
- (NSArray *)elements;
例如:
- (void)addLayoutManager:(NSLayoutManager *)adObj;
- (void)removeLayoutManager:(NSLayoutManager *)anObj;
- (NSArray *)layoutManagers;
集合方法命名有如下一些限制和约定:
如果将元素插入指定位置的功能很重要,则需具备如下方法:
- (void)insertElement:(elementType)anObj atIndex:(int)index;
- (void)removeElementAtIndex:(int)index;
集合方法的实现要考虑如下细节:
当被插入的对象需要持有指向集合对象的指针时,通常使用 set... 来命名其设置该指针的方法,且不要 retain 集合对象。比如上面的 insertLayerManager: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;
命名方法参数时要考虑如下规则:
按照 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
大多数情况下,私有方法命名相同与公共方法命名约定相同,但通常我们约定给私有方法添加前缀,以便与公共方法区分开来。即使这样,私有方法的名称很容易导致特别的问题。当你设计一个继承自 Cocoa framework 某个类的子类时,你无法知道你的私有方法是否不小心覆盖了框架中基类的同名方法。
Cocoa framework 的私有方法名称通常以下划线作为前缀(如:_fooData),以标示其私有属性。基于这样的事实,遵循以下两条建议:
尽管为私有方法名称添加前缀的建议与前面类中方法命名的约定冲突,这里的意图有所不同:为了防止不小心地覆盖基类中的私有方法。
Objective-C 允许通过函数(C 形式的函数)描述行为,就如成员方法一样。如果隐含的类为单例或在处理函数子系统时,你应当优先使用函数,而不是类方法。
函数命名应该遵循如下几条规则:
函数命名与方法命名相似,但有两点不同:
大多数函数名称以动词开头,这个动词描述该函数的行为
NSHighlightRect
NSDeallocateObject
查询属性的函数有个更多的规则要遵循:
查询第一个参数的属性的函数,省略动词
unsigned int NSEventMaskFromType(NSEventType type)
float NSHeight(NSRect rect)
返回值为引用的方法,使用 Get
const char *NSGetSizeAndAlignment(const char *typePtr, unsigned int *sizep, unsigned int *alignp)
返回 boolean 值的函数,名称使用判断动词 is, does 开头
BOOL NSDecimalIsNotANumber(const NSDecimal *decimal)
这一节描述属性,实例变量,常量,异常以及通知的命名约定。
属性声明的命名大体上和访问方法的命名是一致的。假如属性是动词或名词,格式如下
@property (...) typenounOrVerb;
例如:
@property (strong) NSString *title;
@property (assign) BOOL showsAlpha;
如果属性是形容词,名字去掉"is"前缀,但是要特别说明一下符合规范的get访问方法,例如
@property (assign, getter=isEditable) BOOL editable;
多数情况下,你声明了一个属性,那么就会自动生成对应的实例变量。
确保实例变量名简明扼要地描述了它所代表的属性。通常,你应该使用访问方法,而不是直接访问实例变量(除了在init或者dealloc方法里)。为了便于标识实例变量,在名字前面加个下划线"_",例如:
@implementation MyClass {
BOOL _showsTitle;
}
在为类添加实例变量是要注意:
@private
,@protected
显式限定实例变量的访问权限常量命名规则根据常量创建的方式不同而不同。
枚举常量与其 typedef
命名遵守函数命名规则。如:来自 NSMatrix.h 中的例子
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
};
const
来修饰浮点数常数,以及彼此没有关联的整数常量(否则使用枚举)const常量命名范例:
const float NSLightGray;
枚举常量命名规则与函数命名规则相同。
#define
来创建常量。如上面所述,整数常量请使用枚举,浮点数常量请使用const
使用大写字母来定义预处理编译宏。如:
#ifdef DEBUG
编译器定义的宏名首尾都有双下划线。 如:
__MACH__
为 notification 名及 dictionary key 定义字符串常量,从而能够利用编译器的拼写检查,减少书写错误。Cocoa 框架提供了很多这样的范例:
APPKIT_EXTERN NSString *NSPrintCopies;
实际的字符串值在实现文件中赋予。(注意: APPKIT_EXTERN
宏等价于 Objective-C 中 extern
)
异常与通知的命名遵循相似的规则,但是它们有各自推荐的使用模式。
虽然你可以出于任何目的而使用异常(由 NSException 类及相关类实现),Cocoa 通常不使用异常来处理常规的,可预料的错误。在这些情形下,使用诸如 nil
, NULL
, NO
或错误代码之类的返回值。异常的典型应用类似数组越界之类的编程错误。
异常由具有如下形式的全局 NSString 对象标识:
[Prefix] + UniquePartOfName + Exception
UniquePartOfName 部分是有连续的首字符大写的单词组成。例如:
NSColorListIOException
NSColorListNotEditableException
NSDraggingException
NSFontUnavailableException
NSIllegalSelectorException
如果一个类有委托,那它的大部分通知可能由其委托的委托方法来处理。这些通知的名称应该能够反应其响应的委托方法。比如当应用程序提交 NSApplicationDidBecomeActiveNotification
通知时,全局 NSApplication 对象的委托会注册从而能够接收applicaitonDidBecomeActive:
消息。
通知由具有如下形式的全局 NSString 对象标识:
[相关联类的名称] + [Did 或 Will] + [UniquePartOfName] + Notification
例如:
NSApplicationDidBecomeActiveNotification
NSWindowDidMiniaturizeNotification
NSTextViewDidChangeSelectionNotification
NSColorPanelColorDidChangeNotification
图片文件命名采用type_location_identifier_state
规则,只需要@2x
和@3x
图片。
缩略前缀可用如下例子
参考:
UIImage *settingIcon = [UIImage imageNamed:@"icon_common_setting"];
图片目录中被用于类似目的的图片应归入各自的组中。
在设计编程接口时,通常名称不要缩写。然而下面列出的缩写要么是固定下来的,要么是过去被广泛使用的,所以你可以继续使用。关于缩写有一些额外的注意事项:
缩写 | 含义 |
---|---|
alloc | Allocate |
alt | Alternate |
app | Application |
calc | Calculate |
dealloc | Deallocate |
func | Function |
horiz | Horizontal |
info | Information |
init | Initialize |
int | Integer |
max | Maximum |
min | Minimum |
msg | Message |
nib | Interface Builder archive |
pboard | Pasteboard |
rect | Rectangle |
Rep | Representation |
temp | Temporary |
vert | Vertical |
ASCII
PDF
XML
HTML
URL
RTF
HTTP
TIFF
JPG
GIF
LZW
ROM
RGB
CMYK
MIDI
FTP
当需要的时候,注释应该被用来解释为什么特定代码做了某些事情。所使用的任何注释必须保持最新,否则就删除掉。
通常应该避免一大块注释,代码就应该尽量作为自身的文档,只需要隔几行写几句说明。这并不适用于那些用来生成文档的注释。
采用Xcode自动生成的注释格式,修改部分参数:
//
// AppDelegate.m
// LeFit
//
// Created by Zhang Wen on 21-3-22.
// Copyright (c) 2021 Appscomm LLC. All rights reserved.
//
其中项目名称、创建人、公司/组织版权需要填写正确。
如果有一个以上的 import 语句,就对这些语句进行分组。每个分组的注释是可选的。
注:对于模块使用@import
语法。
// Frameworks
@import QuartzCore;
// Models
#import "NYTUser.h"
// Views
#import "NYTButton.h"
#import "NYTUserView.h"
采用Javadoc的格式,可以使用Xcode插件VVDocumenter-Xcode快速添加,只需输入///
即可
Xcode 8后请使用自带扩展插件,快捷键Option
+Command
+/
/**
<#Description#>
@param tableView <#tableView description#>
@param section <#section description#>
@return <#return value description#>
*/
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return [self.familyNames objectAtIndex:section];
}
单行的用//
+空格
开头,多行的采用/* */
注释
TODO 很不错, 有时候注释确实是为了标记一些未完成的或完成的不尽如人意的地方, 这样一搜索就知道还有哪些活要干, 日志都省了。
格式://TODO: 说明
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//TODO: 增加初始化
return YES;
}
实现文件中的代码结构,提倡以下约定:
#pragma mark -
将函数或方法按功能进行分组。这样是为了时刻提醒你要记得释放相关资源。
delgate或协议相关方法放到一般内容之后。
#pragma mark - Lifecycle
- (void)dealloc {}
- (instancetype)init {}
- (void)viewDidLoad {}
- (void)viewWillAppear:(BOOL)animated {}
- (void)didReceiveMemoryWarning {}
#pragma mark - Custom Accessors
- (void)setCustomProperty:(id)value {}
- (id)customProperty {}
#pragma mark - Protocol conformance
#pragma mark - UITextFieldDelegate
#pragma mark - UITableViewDataSource
#pragma mark - UITableViewDelegate
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone {}
#pragma mark - NSObject
- (NSString *)description {}
应该 始终 使用点语法来访问或者修改属性,访问其他实例时首选括号。
推荐:
view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate;
反对:
[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate;
if
else
switch
while
等)始终和声明在同一行开始,在新的一行结束。推荐:
if (user.isHappy) {
// Do something
}
else {
// Do something else
}
@synthesize
和@dynamic
在实现中每个都应该占一个新行。如果某个函数或方法的实现代码过长,可以考量下是否可以将代码拆分成几个小的拥有单一功能的方法。
30行是在13寸macbook上XCode用14号字体时,恰好可以让一个函数的代码做到整屏完全显示的行数。
为了简洁和便于阅读,建议将单个实现文件的代码行数控制在500~600行以内最好。
当接近或超过800行时,就应当开始考虑分割实现文件了。
最好不要出现代码超过1000行的实现文件。
我们一般倾向于认为单个文件代码行数越长,代码结构就越不好。而且,翻代码翻的手软啊。
可以使用Objective-C的Category特性将实现文件归类分割成几个相对轻量级的实现文件。
条件判断主体部分应该始终使用大括号{}
括住来防止出错,即使它可以不用大括号(例如它只需要一行)。这些错误包括添加第二行(代码)并希望它是 if 语句的一部分时。还有另外一种更危险的,当 if 语句里面的一行被注释掉,下一行就会在不经意间成为了这个 if 语句的一部分。此外,这种风格也更符合所有其他的条件判断,因此也更容易检查。
推荐:
if (!error) {
return success;
}
反对:
if (!error)
return success;
或
if (!error) return success;
三目运算符?
,只有当它可以增加代码清晰度或整洁时才使用。单一的条件都应该优先考虑使用。多条件时通常使用 if 语句会更易懂,或者重构为实例变量。
推荐:
result = a > b ? x : y;
反对:
result = a > b ? x = c > d ? c : d : y;
当引用一个返回错误参数(error parameter)的方法时,应该针对返回值,而非错误变量。
推荐:
NSError *error;
if (![self trySomethingWithError:&error]) {
// 处理错误
}
反对:
NSError *error;
[self trySomethingWithError:&error];
if (error) {
// 处理错误
}
一些苹果的 API 在成功的情况下会写一些垃圾值给错误参数(如果非空),所以针对错误变量可能会造成虚假结果(以及接下来的崩溃)。
-
+
符号后应该有一个空格。方法片段之间也应该有一个空格。推荐:
- (void)setExampleText:(NSString *)text image:(UIImage *)image;
实现文件中,方法的左花括号不另起一行,和方法名同行,并且和方法名之间保持1个空格
此条是为了和Xcode6.1模板生成的文件的代码风格保持一致。
//赞成的
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
//不赞成的
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
变量名应该尽可能命名为描述性的。除了for()
循环外,其他情况都应该避免使用单字母的变量名。 星号*
表示指针属于变量,例如:NSString *text
不要写成NSString* text
或者NSString * text
,常量除外。 尽量定义属性来代替直接使用实例变量。除了初始化方法(init
,initWithCoder:
,等),dealloc
方法和自定义的 setters 和 getters 内部,应避免直接访问实例变量。更多有关在初始化方法和 dealloc 方法中使用访问器方法的信息,参见这里。
推荐:
@interface NYTSection: NSObject
@property (nonatomic) NSString *headline;
@end
反对:
@interface NYTSection : NSObject {
NSString *headline;
}
当在ARC 中引入变量限定符时, 限定符 (__strong
,__weak
,__unsafe_unretained
,__autoreleasing
) 应该位于星号和变量名之间,如:NSString * __weak text
。
block
适合用在 target,selector 模式下创建回调方法时,因为它使代码更易读。块中的代码应该缩进4
个空格。 取决于块的长度,下列都是合理的风格准则:
4
空格缩进。20
行,建议把它定义成一个局部变量,然后再使用该变量。如果块不带参数,^{
之间无须空格。如果带有参数,^(
之间无须空格,但) {
之间须有一个空格。
// The entire block fits on one line.
[operation setCompletionBlock:^{ [self onOperationDone]; }];
[operation setCompletionBlock:^{
[self.delegate newDataAvailable];
}];
dispatch_async(fileIOQueue_, ^{
NSString *path = [self sessionFilePath];
if (path) {
...
}
});
[[SessionService sharedService]
loadWindowWithCompletionBlock:^(SessionWindow *window) {
if (window) {
[self windowDidLoad:window];
}
else {
[self errorLoadingWindow];
}
}
];
[[SessionService sharedService]
loadWindowWithCompletionBlock:
^(SessionWindow *window) {
if (window) {
[self windowDidLoad:window];
}
else {
[self errorLoadingWindow];
}
}
];
void (^largeBlock)(void) = ^{
...
};
[operationQueue_ addOperationWithBlock:largeBlock];