Objective-C 编码规范,内容来自苹果的文档翻译,自己的编码经验和对其它资料的总结。
一.命名规范
基本原则
1.清晰
命名应该尽可能的清晰和简洁,但在 Objective-C 中,清晰比简洁更重要。由于 Xcode 强大的自动补全功能,我们不必担心名称过长的问题。
// 清晰
insertObject:atIndex:
// 不清晰, insert 的对象类型和 at 的位置属性没有说明
insert:at:
// 清晰
removeObjectAtIndex:
// 不清晰, remove 的对象类型没有说明,参数的作用没有说明
remove:
不要使用单词的简写,拼写出完整的单词:
// 清晰
setBackgroundColor:
// 不清晰,不要使用简写
setBkgdColor:
然而,有部分单词简写在 Objective-C 编码过程中是非常常用的,以至于成为了一种规范,这些简写可以在代码中直接使用,下面列举了部分
alloc == Allocate
max == Maximum
alt == Alternate
min == Minimum
app == Application
msg == Message
calc == Calculate
nib == Interface Builder archive
dealloc == Deallocate
pboard == Pasteboard
func == Function
rect == Rectangle
horiz == Horizontal
Rep == Representation (used in class name such as NSBitmapImageRep).
info == Information
temp == Temporary
init == Initialize
vert == Vertical
int == Integer
命名方法或者函数时要避免歧义
功能的英文词义命名, 以它[做什么]来命名,而不是[怎么做]来命名
// 有歧义,是返回 sendPort 还是 send 一个 Port ?
sendPort
// 有歧义,是返回一个名字属性的值还是 display 一个 name 的动作?
displayName
// 正确 很明显这个函数是返回一个消息的时间戳
getAmsgTimeStamp;
2.一致性
整个工程的命名风格要保持一致性,最好和苹果 SDK 的代码保持统一。不同类中完成相似功能的方法应该叫一样的名字,比如我们总是用 count 来返回集合的个数,不能在 A 类中使用 count 而在 B 类中使用 getNumber 。
- 如果代码需要打包成 Framework 给别的工程使用,或者工程项目非常庞大,需要拆分成不同的模块,使用命名前缀是非常有用的。
- 可以在为类、协议、函数、常量以及 typedef 宏命名的时候使用前缀,但注意不要为成员变量或者方法使用前缀,因为他们本身就包含在类的命名空间内。
- 命名前缀的时候不要和苹果 SDK 框架冲突
前缀由大写的字母缩写组成,比如 Cocoa 中 NS 前缀代表 Founation 框架中的类, IB 则代表 Interface Builder 框架。
所以要选择符合自己项目的前缀,
执行准则
1.命名类和协议( Class&Protocol )
类名以大写字母开头,应该包含一个名词来表示它代表的对象类型,同时可以加上必要的前缀,比如 NSString,NSDate,NSScanner,NSApplication 等等。
协议名称应该清晰地表示它所执行的行为,而且要和类名区别开来,所以通常使用 ing 词尾来命名一个协议,比如 NSCopying,NSLocking 。
有些协议本身包含了很多不相关的功能,主要用来为某一特定类服务,这时候可以直接用类名来命名这个协议,比如 NSObject 协议,它包含了 id 对象在生存周期内的一系列方法。
2.命名头文件( Headers )
源码的头文件名应该清晰地暗示它的功能和包含的内容:
- 如果头文件内只定义了单个类或者协议,直接用类名或者协议名来命名头文件,比如 NSLocale.h 定义了 NSLocale 类。
- 如果头文件内定义了一系列的类、协议、类别,使用其中最主要的类名来命名头文件,比如 NSString.h 定义了 NSString 和 NSMutableString 。
- 每一个 Framework 都应该有一个和框架同名的头文件,包含了框架中所有公共类头文件的引用,比如 Foundation.h
- Framework 中有时候会实现在别的框架中类的类别扩展,这样的文件通常使用被扩展的框架名 +Additions 的方式来命名,比如 NSBundleAdditions.h 。
3.命名方法( Methods )
Objective-C 的方法名通常都比较长,这是为了让程序有更好地可读性,按苹果的说法 “_ 好的方法名应当可以以一个句子的形式朗读出来_ ” 。
方法一般以小写字母打头,每一个后续的单词首字母大写,方法名中不应该有标点符号(包括下划线),有两个例外:
- 可以用一些通用的大写字母缩写打头方法,比如 PDF,TIFF 等。
- 可以用带下划线的前缀来命名私有方法或者类别中的方法。
如果方法表示让对象执行一个动作,使用动词打头来命名,注意不要使用 do , does 这种多余的关键字,动词本身的暗示就足够了:
// 动词打头的方法表示让对象执行一个动作
- (void)invokeWithTarget:(id)target;
- (void)selectTabViewItem:(NSTabViewItem *)tabViewItem;
如果方法是为了获取对象的一个属性值,直接用属性名称来命名这个方法,注意不要添加 get 或者其他的动词前缀:
// 正确,使用属性名来命名方法
- (NSSize)cellSize;
// 错误,添加了多余的动词前缀
- (NSSize)calcCellSize;
- (NSSize)getCellSize;
对于有多个参数的方法,务必在每一个参数前都添加关键词,关键词应当清晰说明参数的作用不要用 and 来连接两个参数,通常 and 用来表示方法执行了两个相对独立的操作(从设计上来说,这时候应该拆分成两个独立的方法).
方法的参数命名也有一些需要注意的地方 :
- 和方法名类似,参数的第一个字母小写,后面的每一个单词首字母大写
- 不要再方法名中使用类似 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
4.命名常量( Constants )
如果要定义一组相关的常量,尽量使用枚举类型( enumerations ),枚举类型的命名规则和函数的命名规则相同:
//下拉刷新
typedef enum{
HWRefreshStatuPulling = 0,//下拉
HWRefreshStatuRefrshing = 1,//刷新
HWRefreshStatuFinish = 2//刷新完成
}HWRefreshStatus;
//这是SDWebImage的枚举
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
SDWebImageDownloaderLowPriority = 1 << 0,
SDWebImageDownloaderProgressiveDownload = 1 << 1,
/**
* By default, request prevent the of NSURLCache. With this flag, NSURLCache
* is used with default policies.
*/
SDWebImageDownloaderUseNSURLCache = 1 << 2,
/**
* Call completion block with nil image/imageData if the image was read from NSURLCache
* (to be combined with `SDWebImageDownloaderUseNSURLCache`).
*/
SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
/**
* In iOS 4+, continue the download of the image if the app goes to background. This is achieved by asking the system for
* extra time in background to let the request finish. If the background task expires the operation will be cancelled.
*/
SDWebImageDownloaderContinueInBackground = 1 << 4,
/**
* Handles cookies stored in NSHTTPCookieStore by setting
* NSMutableURLRequest.HTTPShouldHandleCookies = YES;
*/
SDWebImageDownloaderHandleCookies = 1 << 5,
/**
* Enable to allow untrusted SSL ceriticates.
* Useful for testing purposes. Use with caution in production.
*/
SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,
/**
* Put the image in the high priority queue.
*/
SDWebImageDownloaderHighPriority = 1 << 7,
};
枚举也要选取适合自己项目的前缀
5.命名通知( Notifications)
通知常用于在模块间传递消息,所以通知要尽可能地表示出发生的事件,通知的命名范式是:
[ k ] + Notification + [ 动作 ] + [Did | Will] + [ 状态 ]
kNotificationMessageDidSendFail
6.文件注释
每一个文件都必须写文件注释,文件注释通常包含:
- 作者信息
- 文件包含的内容,作用
- 历史版本
- 文件所在模块
/*******************************************************************************
Author: Sean Xue (Xue yunqiang)
E-mail: [email protected]
Description:
This file provide some covenient tool in calling library tools. One can easily include
library headers he wants by declaring the corresponding macros.
I hope this file is not only a header, but also a useful Linux library note.
History:
2012-??-??: On about come date around middle of Year 2012, file created as "commonLib.h"
2013-01-07: Add basic data type such as "sint8_t"
2013-01-18: Add CFG_LIB_STR_NUM.
2013-01-22: Add CFG_LIB_TIMER.
2013-01-22: Remove CFG_LIB_DATA_TYPE because there is already AMCDataTypes.h
********************************************************************************/
//
// ViewController.m
// LRMacroDefinition
//
// Created by lu on 16/7/4.
// Copyright © 2016年 scorpio. All rights reserved.
//
7.代码注释
按住Command+Option+/即可得到
/**
<#Description#>
@param imageName <#imageName description#>
@return <#return value description#>
*/
依次注释即可
//代码样式举例
/**
输入图片名获取图片
@param imageName 图片名成
@return UIImage对象
*/
8.import 和 include
import 是 Cocoa 中常用的引用头文件的方式,它能自动防止重复引用文件,什么时候使用 import ,什么时候使用 include 呢?
- 当引用的是一个 Objective-C 或者 Objective-C++ 的头文件时,使用 import
- 当引用的是一个 C 或者 C++ 的头文件时,使用 include ,这时必须要保证被引用的文件提供了保护域( define guard )。
二.代码格式
1.将空格设置为四个空格
在 Xcode > Preferences > Text Editing 将 Tab 和自动缩进都设置为 4 个空格。
2.每一行的最大长度
同样的,在 Xcode > Preferences > Text Editing > Page guide at column: 中将最大行长设置为 80 ,过长的一行代码将会导致可读性问题。
3.函数的书写
一个典型的 Objective-C 函数应该是这样的:
- (void)writeVideoFrameWithData:(NSData *)frameData timeStamp:(int)timeStamp {
...
}
在 - 和 (void) 之间应该有一个空格,第一个大括号 { 的位置在函数所在行的末尾,同样应该有一个空格。
如果一个函数有特别多的参数或者名称很长,应该将其按照 : 来对齐分行显示:
-(id)initWithModel:(IPCModle)model
ConnectType:(IPCConnectType)connectType
Resolution:(IPCResolution)resolution
AuthName:(NSString *)authName
Password:(NSString *)password
MAC:(NSString *)mac
AzIp:(NSString *)az_ip
AzDns:(NSString *)az_dns
Token:(NSString *)token
Email:(NSString *)email
Delegate:(id)delegate;
在分行时,如果第一段名称过短,后续名称可以以 Tab 的长度( 4 个空格)为单位进行缩进:
- (void)short:(int)theFoo
longKeyword:(NSRect)theRect
evenLongerKeyword:(float)theInterval
error:(NSError **)theError;
4.函数调用
函数调用的格式和书写差不多,可以按照函数的长短来选择写在一行或者分成多行:
// 写在一行
[myObject doFooWith:arg1 name:arg2 error:arg3];
// 分行写,按照 ':' 对齐
[myObject doFooWith:arg1
name:arg2
error:arg3];
// 第一段名称过短的话后续可以进行缩进
[myObj short:arg1
longKeyword:arg2
evenLongerKeyword:arg3
error:arg4];
5.闭包( Blocks )
- 较短的 block 可以写在一行内。
- 如果分行显示的话, block 的右括号 } 应该和调用 block 那行代码的第* 一个非空字符对齐。
- block 内的代码采用 4 个空格 的缩进。
- 如果 block 过于庞大,应该单独声明成一个变量来使用。
- ^ 和 ( 之间, ^ 和 { 之间都没有空格,参数列表的右括号 ) 和 { 之间有一个空格。
// 较短的 block 写在一行内
[operation setCompletionBlock:^{ [self onOperationDone]; }];
// 分行书写的 block ,内部使用 4 空格缩进
[operation setCompletionBlock:^{
[self.delegate newDataAvailable];
}];
// 使用 C 语言 API 调用的 block 遵循同样的书写规则
dispatch_async(_fileIOQueue, ^{
NSString* path = [self sessionFilePath];
if (path) {
// ...
}
});
// 较长的 block 关键字可以缩进后在新行书写,注意 block 的右括号 '}' 和调用 block 那行代码的第一个非空字符对齐
[[SessionService sharedService]
loadWindowWithCompletionBlock:^(SessionWindow *window) {
if (window) {
[self windowDidLoad:window];
} else {
[self errorLoadingWindow];
}
}];
// 较长的 block 参数列表同样可以缩进后在新行书写
[[SessionService sharedService]
loadWindowWithCompletionBlock:
^(SessionWindow *window) {
if (window) {
[self windowDidLoad:window];
} else {
[self errorLoadingWindow];
}
}];
// 庞大的 block 应该单独定义成变量使用
void (^largeBlock)(void) = ^{
// ...
};
[_operationQueue addOperationWithBlock:largeBlock];
// 在一个调用中使用多个 block ,注意到他们不是像函数那样通过 ':' 对齐的,而是同时进行了 4 个空格的缩进
[myObject doSomethingWith:arg1
firstBlock:^(Foo *a) {
// ...
}
secondBlock:^(Bar *b) {
// ...
}];
三.代码组织
在函数分组和protocol/delegate实现中使用#pragma mark -来分类方法,要遵循以下一般结构:
#pragma mark - Lifecycle
- (instancetype)init {}
- (void)dealloc {}
- (void)viewDidLoad {}
- (void)viewWillAppear:(BOOL)animated {}
- (void)didReceiveMemoryWarning {}
#pragma mark - Public
- (void)publicMethod {}
#pragma mark - Private
- (void)privateMethod {}
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone {}
#pragma mark - NSObject
- (NSString *)description {}