Objective C 代码规范
该文档的目的在于规范整个项目的代码,使得代码更具可读性,在草拟该文档的时候常照了较多较好的代码规范文档,这些文档将会罗列在该文档参考文献部分。大家如果在使用该文档过程中发现有任何的遗漏或者有更好的规范或者异议都欢迎联系本人(文档末尾有本人的联系邮箱)。
为什么需要有代码规范
一般一个项目代码通常是由一个团队共同开发维护的产物,随着业务的不断扩展,功能的快速迭代,代码会变得越来越庞大,如果这个时候不同的开发者的code风格千差万别,那么阅读代码将会是一件十分痛苦的事情。代码规范除了一些必须遵守的规则外,大多是没有对错而言,它只是让整个项目的代码看起开风格更加统一,我们知道人对熟悉的东西接受起来会快,对于熟悉的代码风格同样也是这个道理,这就是为每个项目拟定代码规范的意义所在.
该规范整体分成三类:
- [命名规范]
- [编码规范]
所有规范分成两个等级
- [必须]
- [建议]
目录
命名规范
- 通用命名规范
- 文件命名规范
- 类命名规范
- 方法命名规范
- Getter/Setter命名规范
- 属性,参数命名规范
- Delegate 命名规范
- Protocol命名规范
- Catogries命名规范
- 常量命名规范
- Exception命名规范
- Notification命名规范
编码规范
- Initialize 规范
- dealloc 规范
- Block规范
- Notification规范
- Collection规范
- 控制语句规范
- 对象判等规范
- 懒加载规范
- 内存管理规范
项目设计规范
- 源码注释规范
- 文件导入规范
- 代码布局规范
- interface接口文件布局规范
- 类设计规范
命名规范
通用命名规范
- [必须] 命名必须具备见名知意的效果,禁止中文拼音,过度缩写,以及一切无意义的命名。
- [必须] 除了通知和掩码常量外命名禁止自我指涉(在变量的末尾增加类型后缀)
- [必须] 参数名、成员变量、局部变量、属性名都要采用小写字母开头的驼峰命名方式。如果方法名以一个众所周知的大写缩略词开始,可以不适用驼峰命名方式。比如FTP、WWW URL等。
- [建议] 不同文件中或者不同类中具有相同功能或相似功能的属性的命名应该是相同的或者相似的。比如:count同时定义在NSDictionary、NSArray、NSSet这三个集合类中。且这三个集合类中的count属性都代表同一个意思,即集合中对象的个数。
- [建议] 一般情况下,不要缩写或省略单词,建议拼写出来,即使它有点长。
文件命名规范
我们在刚拿到代码的时候首先会先看项目的目录结构,其次就是文件的组织,而了解文件的组织就是从文件名开始,所以文件命名也是一个非常重要的工作。
- [必须] 分类文件必须使用分类所依附的主类名 + 分类名称的形式 比如:UIImage+NMAddition.h
- [必须] 一般建议一个文件中只定义一个类,但是如果定义多个的时候,使用最主要的那个类的名称作为文件名。
- [必须] 文件后缀选择:
Extension Type
.h C/C++/Objective-C header file
.m Objective-C implementation file
.mm Objective-C++ implementation file
.cc Pure C++ implementation file
.c C implementation file
类命名规范
- [必须] 类的名称应该由两部分组成,前缀+名称,前缀用大写字符,名称用大写开头的驼峰规则命名。
- [建议] 前缀一般使用项目产品名的缩写,之所以不使用公司组织的缩写是因为一个公司有多个产品,使用公司名来作为前缀有可能导致重复。对于前缀一般使用多于两位的大写字符,因为苹果默认保留了两位字符的缩写前缀,但是这不是必须的。
方法命名规范
- [必须] 方法名必须使用小写开头的驼峰命名方式,如果方法名以一个中所周知的大写缩略词开头,该规则可以忽略。
- [必须] 一般类方法名不需要使用前缀,因为它们存在于特定类的命名空间中,私有方法可以使用统一的前缀来分组和辨识
- [必须] 禁止在方法前面加下划线“ _ ”。Apple官网团队经常在方法前面加下划线”_”。为了避免方法覆盖,导致不可预知的意外,禁止在方法前面加下划线。
- [必须] 如果一个方法代表某个名词执行的动作,则该方法应该以一个动词开头。但不要使用“do”或者”does”作为方法名称的一部分,因为这些助动词不能为方法名称增加太多的意义,反而让方法看起来更加臃肿。同时,也请不要在动词前面使用副词或者形容词。
- [建议] 方法实现时,如果参数过长,则令每个参数占用一行,以冒号对齐,在分行时,如果第一段名称过短,后续名称可以以Tab的长度(4个空格)为单位进行缩进,
-(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<IPCConnectHandlerDelegate>)delegate;
- (void)short:(GTMFoo *)theFoo
longKeyword:(NSRect)theRect
evenLongerKeyword:(float)theInterval
error:(NSError **)theError {
...
} - [建议] 如果一个方法调用语句太长,需要对参数进行冒号对齐,如果只有一个冒号,但是还是太长了,可以换行,并且第二行与第一行的第二个字符对齐。
- [必须] 只有在访问某个属性的时候使用”点访问,其他的使用空格调用。
- [建议] 对输入参数的正确性和有效性进行检查,参数错误立即返回
- [建议] 对于有返回值的方法,每一个分支都必须有返回值。
- [必须] 禁止直接调用NSObject的类方法+new,也不要在子类中重载它。使用alloc和init方法
self.productsRequest = [[SKProductsRequest alloc]
initWithProductIdentifiers:productIdentifiers]; - [必须] 在方法定义的时候需要在-/+符号后面添加一个空格
- (void)invokeWithTarget:(id)target;
- (void)selectTabViewItem:(NSTabViewItem *)tabViewItem - [必须] 如果某个方法返回一个对象,那么名字应该使用返回对象的名字来命名
- [必须] 如果方法返回接收者的某个属性,那么请直接以属性名作为方法名。如果方法间接的返回一个或多个值,我们可以使用“getxxx”的方式来命名方法。相反,无需额外的在方法名前面添加”get”。
- (NSSize)cellSize; OK
- (NSSize)calcCellSize; 不OK
- (NSSize)getCellSize; 不OK - [必须] 所有参数前面都应该添加关键字
- [必须] 尽量使用”with”, “from”, and “to”,进行连接,请不要使用“and”连接接收者属性,但是如果方法描述了两个独立的动作,可以考虑使用“and”连接起来。
- [必须] 方法定义keyword 和参数之间不能有空格
可以写成这样
- (void)setExample:(NSString *)text;
不能写成这样
- (void)setExample: (NSString *)text;
- (void)setExample:(NSString *) text;
Getter/Setter命名规范
- [建议] 如果属性是名词,推荐格式如下:
- (type)noun;
- (void)setNoun:(type)aNoun;
例如:
- (NSString *)title;
- (void)setTitle:(NSString *)aTitle; - [建议] 如果某个属性或者变量的名称是一个形容词,可以省略is前缀,并在属性定义的时候使用getter来指定getter方法的名称。
@property (assign, getter=isEditable) BOOL editable;
- [建议] 如果属性是一个动词,动词使用一般现在时。推荐格式如下:
- (BOOL)verbObject;
- (void)setVerbObject:(BOOL)flag;
例如:
- (BOOL)showsAlpha;
- (void)setShowsAlpha:(BOOL)flag; - [必须] 不要把动词的过去分词形式当做形容词来使用。
- [建议] 可以使用情态动词(can、should、will等)明确方法意义,但不要使用do、does这类无意义的情态动词。
- [建议] 只有方法间接的返回一个数值,或者需要多个数值需要被返回的时候,才有必要在方法名称中使用“get”。像这种接收多个参数的方法应该能够传入nil,因为调用者未必对每个参数都感兴趣
- (void)getLineDash:(float *)pattern count:(int *)count phase:(float *)phase;
属性,参数命名规范
- [建议] 每个属性命名都加上类型后缀,也就是说变量的名称必须同时包含功能与类型,如,按钮就加上Button后缀,模型就加上Model后缀
- [建议] 属性的关键字推荐按照 原子性,读写,内存管理的顺序排列。
@property (nonatomic, readwrite, copy) NSString *name;
@property (nonatomic, readonly, copy) NSString *gender;
@property (nonatomic, readwrite, strong) UIView *headerView; - [必须] Block,NSString属性应该使用copy关键字
- [必须] delegate 应该使用weak关键字
- [必须] 禁止使用synthesize关键词
- [建议] 如果是静态常量,仅限本类内使用的,加上前缀s_,如果是整个工程共用,以sg_为前缀。如:
s_kMaxHeight;
sg_kMaxHeight; - [建议] 对于本地变量,在最靠京它们使用的地方声明,并且在它们声明的同时对其进行初始化
- [建议] 由于32位和64位版本的大小不同,请避免使用long类型,NSInteger,NSUInteger和CGFloat,除非匹配系统接口。类型long,NSInteger,NSUInteger和CGFloat在32位和64位版本之间的大小不同。在处理由系统接口公开的值时,使用这些类型是合适的,但对于大多数其他计算应该避免使用它们。
- [必须] 在使用整形变量存储ID之类的值时,需要使用uint32 uint64这一类,长度不会随着平台版本不同而不同的变量类型。
- [必须] 在遇到遵循某个协议的对象定义的时候,不要在id和协议之间加空格
- [必须] 在定义NSArray和NSDictionary时使用泛型,可以保证程序的安全性:
- [必须] 当使用属性,对象实例变量,应该使用self.形式访问,这样可以确保调用的是具备Setter/Getter方法修饰过的属性值。但是在init和dealloc内建议使用类似_variableName形式的访问方式避免Setter/Getter 带来的副作用,更不要在Setter/Getter 方法中使用self.这样会导致死循环。
- [必须] 在定义属性或者变量的时候指针应该归变量,也就是说建议使用:
NSString *text
而不是
NSString* text
NSString * text - [建议] 私有属性强烈建议写在类的空扩展中
@interface IDLCodeStylePrivateDemo ()
@property (nonatomic, strong, readwrite) NSString *privateProperty;
@end - [必须] 对于对外只读属性仅在.h接口文件中定义并指定其读写属性为readonly,
@interface IDLCodeStylePrivateDemo
@property (nonatomic, strong, readonly) NSString *readonlyProperty;
@end - [必须] 如果在实现内部需要修改该属性需要在空扩展内部添加读写属性为readwrite的同名属性:
@interface IDLCodeStylePrivateDemo ()
@property (nonatomic, strong, readwrite) NSString *readonlyProperty;
@end - [必须] 当变量释放后,需要将变量置为nil,避免因为野指针引起的程序崩溃。
- [必须] 变量在使用前应初始化,防止未初始化的变量被引用。
Delegate 方法命名规范
- [建议] 用delegate做后缀,如
当你的委托的方法过多, 可以拆分数据部分和其他逻辑部分, 数据部分用dataSource做后缀. 如 - [建议] 名称以标示发送消息的对象的类名开头,省略类名的前缀并⼩小写第⼀个字⺟
- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row;
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename; - [建议] 除非delegate方法只有一个参数,即触发delegate方法调用的delegating对象,否则冒号是紧跟在类名后面的。
- (BOOL)applicationOpenUntitledFile:(NSApplication *)sender;
- [建议] ⽤于通知委托对象操作即将发生或已经发⽣的方法名中要使⽤did或will
- [建议] 用于询问委托对象可否执行某操作的⽅法名中可使⽤did或will,但最好使⽤should
- (BOOL)windowShouldClose:(id)sender;
Protocol命名规范
- [建议] 有时候protocol只是声明了一堆相关方法,并不关联class。这种不关联class的protocol使用ing形式以和class区分开来。比如NSLocking而非NSLock。
- [建议] 如果proctocol不仅声明了一堆相关方法,还关联了某个class。这种关联class的protocol的命名取决于关联的class,然后再后面再加上protocol或delegate用于显示的声明这是一份协议。
- [必须] 用optional修饰可以不实现的方法,用required修饰必须实现的方法
Catogries命名规范
- [必须] category中不要声明属性和成员变量。
- [必须] 避免category中的方法覆盖系统方法。可以使用前缀来区分系统方法和category方法。但前缀不要仅仅使用下划线”_“。
- [必须] 如果一个类比较复杂,建议使用category的方式组织代码。具体可以参考UIView。
常量命名规范
- [必须] 在代码中的常量必须抽成宏或者静态常量,避免使用硬编码内容
枚举常量
- [必须] 使用枚举类型来表示一组相关的整型常量。
typedef NS_ENUM(NSUInteger, VPLeftMenuTopItemType) {
VPLeftMenuTopItemTypeMain = 0,
VPLeftMenuTopItemTypeShows,
VPLeftMenuTopItemTypeSchedule,
VPLeftMenuTopItemTypeWatchLive,
VPLeftMenuTopItemTypeMax,
};
typedef NS_ENUM(NSInteger, RBKGlobalConstants) {
RBKPinSizeMin = 1,
RBKPinSizeMax = 5,
RBKPinCountMin = 100,
RBKPinCountMax = 500,
};
const常量
- [必须] 使用const关键字创建浮点型常量。如果一个整型常量和其他常量不相关,可以使用const来创建,否则,使用枚举类型表示一组相关的整型常量。
static const int kFileCount = 12;
static NSString *const kUserKey = @"kUserKey"; - [必须] 通常情况下,不要使用#define预处理命令创建常量。
- [必须] 对于局限于某编译单元(实现文件)的常量,以字符k开头,例如kAnimationDuration,且需要以static const修饰
推荐这样写:
static const NSTimeInterval kFadeOutAnimationDuration = 0.4;
不推荐这样写:
static const NSTimeInterval fadeOutTime = 0.4; - [必须] 对于定义于类头文件的常量,外部可见,则以定义该常量所在类的类名开头,例如EOCViewClassAnimationDuration, 仿照苹果风格,在头文件中进行extern声明,在实现文件中定义其值
例如 在头文件中声明如下定义
extern const float EOCViewClassAnimationDuration;
在实现文件中做如下声明
const float EOCViewClassAnimationDuration = 18.0;
宏常量
- [必须] #define 预处理定义的常量全部大写,单词间用 _ 分隔
- [必须] 宏定义中如果包含表达式或变量,表达式或变量必须用小括号括起来。
Exception命名规范
- [必须] 异常是用全局的NSString字符串进行标识。命名方式如下:
[Prefix] + [异常模块] + [异常简要描述] + Exception
Notification命名规范
- [必须] notification的命名使用全局的NSString字符串进行标识。命名方式如下:
[Name of associated class] + [Did | Will] + [UniquePartOfName] + Notification
例如:
NSApplicationDidBecomeActiveNotification
NSWindowDidMiniaturizeNotification
NSTextViewDidChangeSelectionNotification
NSColorPanelColorDidChangeNotification - [必须] object通常是指发出notification的对象,如果在发送notification的同时要传递一些额外的信息,请使用userInfo,而不是object。
- [必须] 如果某个通知是为了告知外界某个事件”即将”发生或者”已经”发生,则请在通知名称中使用“will”或者“did”这样的助动词。例
如:
UIKeyboardWillChangeFrameNotification;
UIKeyboardDidChangeFrameNotification;
编码规范
Initialize 规范
initialize类方法先于其他的方法调用。且initialize方法给我们提供了一个让代码once、lazy执行的地方。initialize通常被用于设置class的版本号,initialize方法的调用遵循继承规则(所谓继承规则,简单来讲是指:子类方法中可以调用到父类的同名方法,即使没有调用[super xxx])。如果我们没有实现initialize方法,运行时初次调用这个类的时候,系统会沿着继承链(类继承体系),先后给继承链上游中的每个超类发送一条initialize消息,直到某个超类实现了initlialize方法,才会停止向上调用。因此,在运行时,某个类的initialize方法可能会被调用多次
- [必须] 如果我们想要让initialize方法仅仅被调用一次,那么需要借助于GCD的dispatch_once()
+ (void)initialize {
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
// the initializing code
}
} - [建议] 如果我们想在继承体系的某个指定的类的initialize方法中执行一些初始化代码,可以使用类型检查和而非dispatch_once()
if (self == [NSFoo class]) {
// the initializing code
} - [必须] initialize是由系统自动调用的方法,我们不应该显示或手动调用initialize方法
Init 规范
Objective-C有designated Initializers和secondary Initializers的概念:
指定初始化方法(designated initializer)是提供所有的(最多的)参数的初始化方法,间接初始化方法(secondary initializer)有一个或部分参数的初始化方法。一个类可以有一个或者多个designated Initializers。但是要保证所有的其他secondary initializers都要调用designated Initializers。即:只有designated Initializers才会存储对象的信息。这样的好处是:当这个类底层的某些数据存储机制发生变化时(可能是一些property的变更),只需要修改这个designated Initializers内部的代码即可。无需改动其他secondary Initializers初始化方法的代码。
- [必须] 所有secondary 初始化方法都应该调用designated 初始化方法。
- [必须] 所有子类的designated初始化方法都要调用父类的designated初始化方法。使这种调用关系沿着类的继承体系形成一条链。
- [必须] 如果子类的designated初始化方法与超类的designated初始化方法不同,则子类应该覆写超类的designated初始化方法。(因为开发者很有可能直接调用超类的某个designated方法来初始化一个子类对象,这样也是合情合理的,但使用超类的方法初始化子类,可能会导致子类在初始化时缺失一些必要信息)。
- [必须] 如果超类的某个初始化方法不适用于子类,则子类应该覆写这个超类的方法,并在其中抛出异常。
- [必须] 禁止子类的designated初始化方法调用父类的secondary初始化方法。否则容易陷入方法调用死循环。
如果想在当前类自定义一个新的全能初始化方法,则需要如下几个步骤: - 定义新的指定初始化方法,并确保调用了直接父类的初始化方法。
- 重载直接父类的初始化方法,在内部调用新定义的指定初始化方法。
- 为新的指定初始化方法写文档。
重载父类的初始化方法并在内部调用新定义的指定初始化方法的原因是你不能确定调用者调用的就一定是你定义的这个新的指定初始化方法,而不是原来从父类继承来的指定初始化方法。假设你没有重载父类的指定初始化方法,而调用者却恰恰调用了父类的初始化方法。那么调用者可能永远都调用不到你自己定义的新指定初始化方法了。而如果你成功定义了一个新的指定初始化方法并能保证调用者一定能调用它,你最好要在文档中明确写出哪一个才是你定义的新初始化方法。或者你也可以使用编译器指令__attribute__((objc_designated_initializer))来标记它。// 超类
@interface ParentObject : NSObject
@end
@implementation ParentObject
//designated initializer
- (instancetype)initWithURL:(NSString*)url title:(NSString*)title {
if (self = [super init]) {
_url = [url copy];
_title = [title copy];
}
return self;
}
//secondary initializer
- (instancetype)initWithURL:(NSString*)url {
return [self initWithURL:url title:nil];
}
@end
// 子类
@interface ChildObject : ParentObject
@end
@implementation ChildObject
//designated initializer
- (instancetype)initWithURL:(NSString*)url title:(NSString*)title {
//在designated intializer中调用 secondary initializer,错误的
if (self = [super initWithURL:url]) {
}
return self;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 这里会死循环
ChildObject* child = [[ChildObject alloc] initWithURL:@"url" title:@"title"];
}
@end - [必须] 禁止在init方法中使用self.xxx的方式访问属性。如果存在继承的情况下,很有可能导致崩溃。原因见文章为什么不能在init和dealloc函数中使用accessor方法
- [必须] 校验父类designated初始化方法返回的对象是否为nil。如果初始化当前对象的时候发生了错误,应该给予对应的处理:释放对象,并返回nil。
dealloc 规范
- [必须] 不要忘记在dealloc方法中移除通知和KVO。
- [建议] dealloc 方法应该放在实现文件的最上面,在任何类中,init 都应该直接放在 dealloc 方法的下面,如果有多个初始化方法,应该将指定初始化方法放在最前面,其他初始化方法放在其后。
- [必须] 在dealloc方法中,禁止将self作为参数传递出去,如果self被retain住,到下个runloop周期再释放,则会造成多次释放crash。如下:
- (void)dealloc{
[self unsafeMethod:self];
//因为当前已经在self这个指针所指向的对象的销毁阶段,销毁self所指向的对象已经在所难免。如果在unsafeMethod:中把self放到了autorelease poll中,那么self会被retain住,计划下个runloop周期在进行销毁。但是dealloc运行结束后,self所指向的对象的内存空间就直接被回收了,但是self这个指针还没有销毁(即没有被置为nil),导致self变成了一个名副其实的野指针。
// 到了下一个runloop周期,因为self所指向的对象已经被销毁,会因为非法访问而造成crash问题。
} - [必须] 和init方法一样,禁止在dealloc方法中使用self.xxx的方式访问属性。如果存在继承的情况下,很有可能导致崩溃。
Block规范
- [必须] 调用block时需要对block判空。
- [必须] 注意block潜在的引用循环。
- [建议] 较短的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) {
// ...
}];
Notification规范
- [必须] 当我们使用通知时,必须要思考,有没有更好的办法来代替这个通知。禁止遇到问题就想到通知,把通知作为备选项而非首选项。
- [必须] post通知时,object通常是指发出notification的对象,如果在发送notification的同时要传递一些额外的信息,请使用userInfo,而不是object。
- [必须] 在多线程应用中,Notification在哪个线程中post,就在哪个线程中被转发,而不一定是在注册观察者的那个线程中。如果post消息不在主线程,而接受消息的回调里做了UI操作,需要让其在主线程执行。
说明:每个进程都会创建一个NotificationCenter,这个center通过NSNotificationCenter defaultCenter获取,当然也可以自己创建一个center。NoticiationCenter是以同步(非异步,当前线程,会等待,会阻塞)的方式发送请求。即,当post通知时,center会一直等待所有的observer都收到并且处理了通知才会返回到poster。如果需要异步发送通知,请使用notificationQueue,在一个多线程的应用中,通知会发送到所有的线程中。
Collection规范
- [必须] 不要用一个可能为nil的对象初始化集合对象,否则可能会导致crash。
- [必须] 对插入到集合对象里面的对象也要进行判空。
- [必须] 注意在多线程环境下访问可变集合对象的问题,必要时应该加锁保护。不可变集合(比如NSArray)类默认是线程安全的,而可变集合类(比如NSMutableArray)不是线程安全的。
- [必须] 禁止在多线程环境下直接访问可变集合对象中的元素。应该先对其进行copy,然后访问不可变集合对象内的元素。
- [必须] 注意使用enumerateObjectsUsingBlock遍历集合对象中的对象时,关键字return的作用域。block中的return代表的是使当前的block返回,而非使当前的整个函数体返回。
- [必须] 禁止返回mutable对象,禁止mutable对象作为入参传递。
- [必须] 在访问集合的时候需要做内存操作越界判断
- [建议] 应该使用可读性更好的字面量来构造NSArray,NSDictionary等数据结构,避免使用冗长的alloc,init方法。
- [建议] 如果构造代码写在一行,需要在括号两端留有一个空格,使得被构造的元素于与构造语法区分开来:
//正确,在字面量的"[]"或者"{}"两端留有空格
NSArray *array = @[ [foo description], @"Another String", [bar description] ];
NSDictionary *dict = @{ NSForegroundColorAttributeName : [NSColor redColor] };
//不正确,不留有空格降低了可读性
NSArray* array = @[[foo description], [bar description]];
NSDictionary* dict = @{NSForegroundColorAttributeName: [NSColor redColor]}; - [建议] 如果使用NSMutableDictionary作为缓存,建议使用NSCache代替。
NSCache优于NSDictionary的几点:
当系统资源将要耗尽时,NSCache具备自动删减缓冲的功能。并且还会先删减“最久未使用”的对象。
NSCache不拷贝键,而是保留键。因为并不是所有的键都遵从拷贝协议(字典的键是必须要支持拷贝协议的,有局限性)。
NSCache是线程安全的:不编写加锁代码的前提下,多个线程可以同时访问NSCache。 - [建议] 集合类使用泛型来指定对象的类型。
- [必须] 取下标的时候要判断是否越界。
- [建议] 取第一个元素或最后一个元素的时候使用firtstObject和lastObject
- [建议] 如果构造代码不写在一行内,构造元素需要使用 两个空格 来进行缩进,右括号]或者}写在新的一行,并且与调用字面量那行代码的第一个非空字符对齐,构造字典时,字典的Key和Value与中间的冒号:都要留有一个空格,多行书写时,也可以冒号对齐:
NSArray *array = @[
@"This",
@"is",
@"an",
@"array"
];
NSDictionary *dictionary = @{
NSFontAttributeName : [NSFont fontWithName:@"Helvetica-Bold" size:12],
NSForegroundColorAttributeName : fontColor
};
控制语句规范
- [建议] 相关的赋值语句等号对齐
promotionsEntity.promotionImageStr = activityItemDict[@"promotion_image"];
promotionsEntity.promotionIdNum = activityItemDict[@"promotion_id"];
promotionsEntity.promotionNameStr = activityItemDict[@"promotion_name"];
promotionsEntity.promotionColorStr = activityItemDict[@"promotion_color"]; - [建议] if条件判断语句后面必须要加大括号{}。不然随着业务的发展和代码迭代,极有可能引起逻辑问题。
- [必须] 条件过多,过长的时候应该换行。条件表达式如果很长,则需要将他们提取出来赋给一个BOOL值,或者抽取出一个方法
- [必须] 不要使用过多的分支,要善于使用return来提前返回错误的情况,把最正确的情况放到最后返回。
- [建议] 对于条件语句的真假,因为 nil 解析为 NO,所以没有必要在条件中与它进行比较。永远不要直接和 YES 和 NO进行比较,因为 YES 被定义为 1,而 BOOL 可以多达 8 位。
- [必须]使用switch…case…语句的时候,不要丢掉default:。除非switch枚举。
- [必须] switch…case…语句的每个case都要添加break关键字,避免出现fall-through。
- [必须] 不可在for循环内修改循环变量,防止for循环失去控制。
for (int index = 0; index < 10; index++){
...
logicToChange(index)
} - [建议] 在使用?:的时候不要嵌套多重?: 这里顺便提一个?: 另外一个用得比较多的场景–为某个属性添加默认值:
dotImage = [_delegate pageControl:self selectedImageForDotAtIndex:i] ?: _selectedDotImage;
- [必须] 在访问CGRect 的 x,y,width,height 属性的时候不要直接访问,建议使用CGRectGetxxxx方法。
- [必须] 如果某个方法的错误传递方式包括引用和返回两种方式的时候,建议使用返回的那个值:
建议:NSError *error;
if (![self trySomethingWithError:&error]) {
// Handle Error
}
不建议:
NSError *error;
[self trySomethingWithError:&error];
if (error) {
// Handle Error
}
对象判等规范
- (BOOL)isEqual:(id)object { |
懒加载规范
懒加载适合的场景: |
- [建议] 懒加载本质上就是延迟初始化某个对象,所以,懒加载仅仅是初始化一个对象,然后对这个对象的属性赋值。懒加载中不应该有其他的不必要的逻辑性的代码,如果有,请把那些逻辑性代码放到合适的地方。
- [必须] 不要滥用懒加载,只对那些真正需要懒加载的对象采用懒加载。
- [必须] 如果一个对象在懒加载后,某些场景下又被设置为nil。我们很难保证这个懒加载不被再次触发。
内存管理规范
- [必须] 函数体提前return时,要注意是否有对象没有被释放掉(常见于CF对象),避免造成内存泄露。
- [建议] 请慎重使用单例,避免产生不必要的常驻内存。
- [建议] 除非你清除的知道自己在做什么。否则不建议将UIView类的对象加入到NSArray、NSDictionary、NSSet中。如有需要可以添加到NSMapTable 和 NSHashTable。因为NSArray、NSDictionary、NSSet会对加入的对象做strong引用(即使你把加入的对象进行了weak)。而NSMapTable、NSHashTable会对加入的对象做weak引用。说明:简单的说,NSHashTable相当于weak的NSMutableArray;NSMapTable相当于weak的NSMutableDictionary.
其他规范
- [必须] performSelector:withObject:afterDelay:要在有Runloop的线程里调用,否则调用无法生效。
项目设计规范
文件布局
-.h |
源码注释规范
优秀的代码大部分是可以自描述的,我们完全可以用代码本身来表达它到底在干什么,而不需要注释的辅助。
但并不是说一定不能写注释,有以下三种情况比较适合写注释:
公共接口(注释要告诉阅读代码的人,当前类能实现什么功能)。
涉及到比较深层专业知识的代码(注释要体现出实现原理和思想)。
容易产生歧义的代码(但是严格来说,容易让人产生歧义的代码是不允许存在的)。
除了上述这三种情况,如果别人只能依靠注释才能读懂你的代码的时候,就要反思代码出现了什么问题。
最后,对于注释的内容,相对于“做了什么”,更应该说明“为什么这么做”。[建议] 注释符与注释内容之间要用一个空格进行分割。
较好的注释例子
|
** 方法的注释使用Xcode自带注释快捷键:Commond+option+/ **
文件导入规范
- [建议] 在类的头文件中尽量少引用其他头文件,有时,类A需要将类B的实例变量作为它公共API的属性。这个时候,我们不应该引入类B的头文件,而应该使用向前声明(forward declaring)使用class关键字,并且在A的实现文件引用B的头文件。
- [必须] 使用import 导入Objective-C 和 Objective-C++ 头文件,使用include 来导入C/C++ 头文件
- [建议] 优先导入框架的头文件,再导入自己的头文件,最后导入三方的头文件。每个类别之间使用一个空行隔开,每个类别的头文件导入中使用//类别进行分类。
- [必须] 务必保持头文件导入没有多余的内容,不需要的头文件导入,切记删除。
- [建议] 共同的接口、结构体、常量和数据类型要定义在同一个头文件里
代码布局规范
项目中的代码需要根据每个方法的具体功能使用#pragma mark - 进行分类,#pragma mark - 上空两行,下空一行
|
- [建议] 方法和方法之间建议使用一行空行隔开
- [建议] 方法内部一类的代码之间不能有空行,一类代码之间需要添加空行来隔开,这样可以比较清晰
- (void)awakeFromNib {
UIStoryboard *signatureStoryboard = [UIStoryboard storyboardWithName:@"BBPopoverSignature" bundle:nil];
self.signatureViewController = [signatureStoryboard instantiateViewControllerWithIdentifier:@"BBPopoverSignature"];
self.signatureViewController.modalPresentationStyle = UIModalPresentationPopover;
self.signatureViewController.preferredContentSize = CGSizeMake(BBPopoverSignatureWidth, BBPopoverSignatureHeight);
self.signatureViewController.signatureImageView = self;
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(initiateSignatureCapture)];
[self addGestureRecognizer:tapRecognizer];
} - [必须] 代码块换行,建议使用
if (user.isHappy) {
//Do something
} else {
//Do something else
}
而不是
if (user.isHappy)
{
//Do something
}
else {
//Do something else
} - [必须] 代码块冒号对齐,使用
[UIView animateWithDuration:1.0 animations:^{
// something
} completion:^(BOOL finished) {
// something
}];
而不是
[UIView animateWithDuration:1.0
animations:^{
// something
}
completion:^(BOOL finished) {
// something
}]; - [建议] 在类扩展和实现文件中保证一行的空行
@interface MyClass ()
// Properties - 在这里保证前后行有一个空行
@end
@implementation MyClass
// Body - 在这里保证前后行有一个空行
@end
//这里也要留个空行 - [建议] 在类声明中包含多个protocal,每个protocal占用一行并对齐。
@interface CustomBackButtonViewController () <UITextFieldDelegate,
MyProtocalDelegate,
UITabBarControllerDelegate,
UITabBarDelegate>
interface接口文件布局规范
- [建议] interface接口的排列顺序建议如下:属性, 类方法, 初始化方法, 实例方法.
类设计规范
- [建议] 尽量减少继承,类的继承关系不要超过3层。可以考虑使用category、protocol来代替继承。
- [建议] 把一些稳定的、公共的变量或者方法抽取到父类中。子类尽量只维持父类所不具备的特性和功能。
- [建议] .h文件中的属性尽量声明为只读。
- [建议] .h文件中只暴露出一些必要的类、公开的方法、只读属性;私有类、私有方法和私有属性以及成员变量,尽量写在.m文件中。
- [建议] 如果某个类的方法较多可以考虑把类的实现代码分散到便于管理的多个分类中
[参考文档]
The Objective-C Programming Language
Cocoa Fundamentals Guide
Coding Guidelines for Cocoa
iOS App Programming Guide
Raywenderlich Objective C Style Guide
Robots & Pencils
New York Times
Google
GitHub
Adium
Sam Soffes
CocoaDevCentral
Luke Redpath
Marcus Zarra
一份走心的iOS开发规范前言约定
Coding Guidelines for Cocoa