Objective-C Coding Style Guidelines
参考资料:
• Apple: Coding Guidelines for Cocoa
• Google: Objective-C Style Guide
正文:
• 间距和格式
◦ 指针“*”号的位置
▪ 如:NSString *varName;
◦ 空格 VS tabs
▪ 只允许使用空格,缩进2个空格
▪ 将编辑器设置为1个TAB = 2个字符缩进
◦ 每行的长度
▪ 每行最多不得超过100个字符
▪ 以15寸Macbook Pro的大小,每行100个字符时能最大化地同时容下编辑器和iPhone模拟器
▪ Google的80字符的标准有点少,这导致过于频繁的换行(Objectve-C的代码一般都很长)
▪ 通过 “Xcode => Preferences => TextEditing => 勾选Show Page Guide / 输入 100=> OK” 来设置提醒
◦ 方法的声明和定义
▪ 在 - OR + 和返回值之间留1个空格,方法名和第一个参数间不留空格。如:
- (void)doSomethingWithString:(NSString *)theString {
...
}
▪ 当参数过长时,每个参数占用一行,以冒号对齐。如:
-(void)doSomethingWith:(GTMFoo *)theFoo
rect:(NSRect)theRect
interval:(float)theInterval {
...
}
▪ 如果方法名比参数名短,每个参数占用一行,至少缩进4个字符,且为垂直对齐(而非使用冒号 对齐)。如:
- (void)short:(GTMFoo *)theFoo
longKeyword:(NSRect)theRect
evenLongerKeyword:(float)theInterval {
...
}
◦ 方法的调用
▪ 调用方法沿用声明方法的习惯。例外:如果给定源文件已经遵从某种习惯,继续遵从那种习惯。
▪ 所有参数应在同一行中,或者每个参数占用一行且使用冒号对齐。如:
[myObject doFooWith:arg1 name:arg2 error:arg3];
或
[myObject doFooWith:arg1
name:arg2
error:arg3];
▪ 和方法的声明一样,如果无法使用冒号对齐时,每个参数一行、缩进4个字符、垂直对齐(而非 使用冒号对齐)。如:
[myObj short:arg1
longKeyword:arg2
evenLongerKeyword:arg3];
◦ @public 和 @private
▪ @public 和@private使用单独一行,且缩进1个字符
◦ Protocals
▪ 类型标示符、代理名称、尖括号间不留空格。
▪ 该规则同样适用于:类声明、实例变量和方法声明。如:
@interface MyProtocoledClass : NSObject<NSWindowDelegate> {
@private
id<MyFancyDelegate> _delegate;
}
-(void)setDelegate:(id<MyFancyDelegate>)aDelegate;
@end
▪ 如果类声明中包含多个protocal,每个protocal占用一行,缩进2个字符。如:
@interface CustomViewController :ViewController<
AbcDelegate,
DefDelegate > {
...
}
• 命名
• 命名要清晰明确, 胜过于简洁
文件名:可以包含被扩展的类的名字,如:GTMNSString+Utils.h GTMNSTextView+Autocomplete.h。
文件名应该反映了它实现了什么类。遵守你的项目的惯例。
分类命名:
应用程序级的代码,应该尽量避免不必要的前缀。为每个类都添加前缀不会提高任何的可读性。当设计跨不同应用程序的代码时,应该使用前缀,例如:GTMSendMessage。 总结:类名、分类名、协议名应该以大写字母开始,并混合小写字母来分隔单词。(Camel命名法)
变量名:
▪ 变量名应使用容易意会的应用全称,且首字母小写,且使用首字母大写的形式分割单词。
▪ 成员变量使用“_”作为前缀(如:“NSString *_varName;”。虽然这与苹果的标准(使用“_”作为后缀)相冲突,但基于以下原因,仍使用“_”作为前缀:
使用“_”作为前缀,更容易在有代码自动补全功能的IDE中区分“属性(self.userInfo)”和“成员变量(_userInfo)”
▪ 常量(#define,enums, const等)使用小写“k”作为前缀,首字母大写来分割单词。如: kInvalidHandle
错误的命名:
int w;
int nerr; intnCompConns;
tix =[[NSMutableArray alloc] init];
obj = [someObjectobject]; p = [network port];
正确的命名:
int numErrors;
intnumCompletedConnections;
tickets =[[NSMutableArray alloc] init];
userInfo = [someObject object];
port = [networkport];
insertObject:atIndex: 好
insert:at: 不明确
removeObjectAtIndex: 好
removeObject: 也可以
remove: 不明确
变量命不能以数字开头,不能添加空格,不能添加除下划线以外的特殊字符.
零碎:
Prefixes
NS: Foundation
NS: Application Kit
AB: Address Book
IB: Interface Builder
宏命名:
和 ANSIC一样 预处理宏定义都使用大写
类似NSUserDefaults key值使用宏,一直存在于固定头文件中 存储的数据加以注释
◦ Category Name
▪ 分类名应该以2~3个字符作为前缀,并且包含所扩展的类名
例如,我们想创建一个NSString的分类用于parsing,就可以将该分类的文件名命名为
GTMNSString+Parsing.h,分类名命名为:GTMStringParsingAdditions。分类的方法应该使用分
类名的前缀(如:gtm_myCategoryMethodOnAString:)
◦ 方法名
▪ 方法名的首字母小写,且使用首字母大写的形式分割单词。方法的参数使用相同的规则。
▪ 方法名+参数应尽量读起来像一句话(如:)。在这里查看苹果对方法命名的规范。
▪ getter的方法名和变量名应相同。不允许使用“get”前缀。如:
- (id)getDelegate; // 错误
- (id)delegate; // 正确
• 注释
1.文件注释
版权信息及作者
每个文件应该按顺序包括如下内容:
版权信息声明(如:Copyright 2008 Google Inc.) 授权样版。选择一个合适的项目所使用的授权样板(例如,Apache 2.0, BSD, LGPL, GPL)。
如果你对其他人的原始代码作出重大的修改,请把你的名字添加到作者里面。当另外一个代码贡献者对文件有问题时,他需要知道怎么联系你,这十分有用。
总结:以版权信息作为文件头部,开始每一个文件,后接文件内容的描述。
2.声明注释
// Adelegate for NSApplication to handle notifications about app // launch andshutdown. Owned by the main app controller. @interface MyAppDelegate : NSObject{
... }
@end
如果你已经在文件头部详细描述了接口,可以直接说明“完整的描述请参见文件头部”,但是一定要 有这部分注释。
另外,公共接口的每个方法,都应该有注释来解释它的作用、参数、返回值以及其它影响。
为类的线程安全性作注释,如果有的话。如果类的实例可以被多个线程访问,记得注释多线程条件
下的使用规则。
总结:每个接口、类别以及协议应该注释,以描述它的目的及作用。
3.实现注释
这会避免二义性,尤其是当符号是一个常用词汇,这使用语句读起来很糟糕。例如,对于符 号"count":
// Sometimeswe need |count| to be less than zero.
或者当引用已经包含引号的符号:
// Rememberto call |StringWithoutSpaces("foo bar baz")|
总结:使用|来引用注释中的变量名及符号名而不是使用引号。
• Cocoa 和 Objective-C特有的规则
◦ 成员变量使用@private。如:
@interface MyClass : NSObject {
@private
id _myInstanceVariable;
}
// public accessors, setter takes ownership
- (id)myInstanceVariable;
- (void)setMyInstanceVariable:(id)theVar;
@end
◦ Indentify Designated Initializer
◦ Override Desingated Initializer
◦ 初始化
▪ 在初始化方法中,不要将变量初始化为“0”或“nil”,那是多余的
内存中所有的新创建的对象(isa除外)都是0,所以不需要重复初始化为“0”或“nil”
◦ 避免使用new方法
▪ 禁止直接调用 NSObject 的类方法 +new,也不要在子类中重载它。使用alloc和init方法
◦ 保持公共API的简洁性
▪ 保持类的简洁,如果一个方法是不需要公开的,那就别公开。可以使用一个私有的分类来使公开的API简洁,例如:
// GTMFoo.m
#import "GTMFoo.h"
@interface GTMFoo (PrivateDelegateHandling)
- (NSString *)doSomethingWithDelegate; // Declare private method
@end
@implementation GTMFoo(PrivateDelegateHandling)
...
- (NSString *)doSomethingWithDelegate {
// Implement this method
}
...
@end
◦ #import VS #include
▪ 使用 #import 引入Ojbective-C和Ojbective-C++头文件,使用 #include引入C和C++头
文件
◦ import根框架(root frameworks),而非各单个文件
▪ 虽然有时我们仅需要框架(如Cocoa 或 Foundation)的某几个头文件,但引入根文件编译
器会运行的更快。因为根框架(root frameworks)一般会预编译,所以加载会更快。再次强 调:使用 #import 而非 #include 来引入Objective-C框架。如:
#import <Foundation/NSArray.h> //错误
#import <Foundation/NSString.h>
...
#import <Foundation/Foundation.h> //正确
◦ 创建临时对象时尽量使用autorelease
▪ 创建临时对象时,尽量同时在同一行中 autorelease 掉,而非使用单独的 release 语句 。
虽然这样会稍微有点慢,但这样可以阻止因为提前 return 或其他意外情况导致的内存泄露。通盘来看这是值得的。如:
// 避免这样使用(除非有性能的考虑)
MyController* controller = [[MyController alloc] init];
// ... 这里的代码可能会提前return ...
[controller release];
// 这样更好
MyController* controller = [[[MyController alloc] init] autorelease];
◦ 先autorelease,再retain
▪ 在为对象赋值时,遵从“先autorelease,再retain” 。
在将一个新创建的对象赋给变量时,要先将旧对象release掉,否则会内存泄露。市面上有很多方法来handle这种情况,这里选择“先autorelease,再retain”的方法,这种方法不易引入error。
注意:在循环中这种方法会“填满”autorelease pool,稍稍影响效率,但是 Google认为
这个代价是可以接受的。如:
- (void)setFoo:(GMFoo *)aFoo {
[_foo autorelease]; // 如果_foo和aFoo是同一个对象(_foo == aFoo),dealloc不会被调用
_foo = [aFoo retain];
}
◦ self格式的strong型变量
retain型全局变量,使用self形式定义,以nil形式释放,简单、安全。
◦ dealloc的顺序要与变量声明的顺序相同
▪ 这有利于review代码
如果dealloc中调用其他方法来release变量,将被release的变量以注释的形式标注清楚
◦ NSString的属性的setter使用“copy”
▪ 禁止使用retain,以防止意外的修改了NSString变量的值。如:
- (void)setFoo:(NSString *)aFoo { [foo_autorelease];
foo_ = [aFoo copy];
}
或
@property (nonatomic, copy) NSString *aString;
◦ 避免抛出异常(Throwing Exceptions)
▪ 待完善
◦ 对 nil 的检查
▪ 仅在有业务逻辑需求时检查 nil,而非为了防止崩溃
向 nil发送消息不会导致系统崩溃,Objective-C运行时负责处理。
◦ BOOL陷阱
▪ 将int值转换为BOOL时应特别小心。避免直接和YES比较
▪ Objective-C中,BOOL被定义为unsigned char,这意味着除了YES (1) 和 NO (0)外它还可以是其他值。禁止将int直接转换(cast or convert)为BOOL。
▪ 常见的错误包括:将数组的大小、指针值或位运算符的结果转换(cast or convert)为 BOOL,因为该BOOL值的结果取决于整型值的最后一位
▪ 将整型值转换为BOOL的方法:使用三元运算符返回YES/ NO,或使用位运算符(&&,||,!)
▪ BOOL、_Bool和bool之间的转换是安全的,但是BOOL和Boolean间的转换不是安全的,所以将Boolean看成整型值。
▪ 在Objective-C中,只允许使用BOOL
▪ 如:
// 错误
- (BOOL)isBold {
return [self fontTraits] & NSFontBoldTrait;
}
- (BOOL)isValid {
return [self stringValue];
}
// 正确
- (BOOL)isBold {
return ([self fontTraits] & NSFontBoldTrait) ? YES : NO;
}
- (BOOL)isValid {
return [self stringValue] != nil;
}
- (BOOL)isEnabled {
return [self isValid] && [self isBold];
}
▪ 禁止直接将BOOL和YES/NO比较,如:// 错误
BOOL great = [foo isGreat];
if (great == YES)
...
// 正确
BOOL great = [foo isGreat];
if (great)
...
◦ 属性
▪ 命名:与去掉“_”前缀的成员变量相同,使用@synthesize将二者联系起来。如:
// abcd.h
@interface MyClass : NSObject{
@private
NSString *_name;
}
@property (copy, nonatomic) NSString *name;
@end
// abcd.m
@implementation MyClass @synthesize name = _name; @end
▪ 位置:属性的声明紧随成员变量块之后,无缩进。如上例所示
▪ 严把权限:对不需要外部修改的属性使用readonly
▪ NSString使用copy而非retain
▪ CFType使用@dynamic, 禁止使用@synthesize
▪ 除非必须,使用nonatomic
• Cocoa Pattern
◦ Delegate Pattern(委托)
▪ delegate对象使用assign,禁止使用retain。因为retain会导致循环索引导致内存泄露,并且此类型的内存泄露无法被Instrument发现,极难调试
▪ 成员变量命名为_delegate,属性名为delegate
◦ Model/View/Controller
▪ Model和View分离
▪ Controller独立于View和Model
▪ 不要在与view相关的类中添加过多的业务逻辑代码,这让代码的可重用性很差
▪ Controller负责业务逻辑代码,且Controller的代码与view尽量无关
▪ 使用 @protocal 定义回调APIs,如果并非所有方法都是必须的,使用 @optional标示
• 其他
◦ init方法和dealloc方法是是最常用的方法,所以将他们放在类实现的开始位置
◦ 使用空格将相同的变量、属性对齐,使用换行分组