-
Notifications
You must be signed in to change notification settings - Fork 6
objcguide
Objective-C是一个C语言的扩展语言,非常动态,非常的“面向对象”,它被设计成既拥有复杂的面向对象设计理念又可以轻松使用与阅读的语言,也是Mac OS X和iPhone开发的首选语言。
Cocoa是Mac OS X的主要应用框架,提供迅速开发各种功能的Mac OS X应用的Objective-C类集合。
注意本文不是Objective-C的教学指南,我们假设读者已经了解语言。如果你是一个Objective-C的初学者或需要重温,请阅读The Objective-C Programming Language.
人们说一个例子胜过千言万语,所以就让我们用例子来让你感受以下编码规范的风格,留间距,命名等等。
下例是一份头文件,展示对@interface 声明正确的注释和留间距
// GTMFoo.h
// FooProject
//
// Created by Greg Miller on 6/13/08.
// Copyright 2008 Google, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
// A sample class demonstrating good Objective-C style. All interfaces,
// categories, and protocols (read: all top-level declarations in a header)
// MUST be commented. Comments must also be adjacent to the object they're
// documenting.
//
// (no blank line between this comment and the interface)
@interface GTMFoo : NSObject
@property (strong, nonatomic) NSString *foo;
@property (strong, nonatomic) NSString *bar;
// Returns an autoreleased instance of GMFoo. See -initWithString: for details
// about the argument.
+ (id)fooWithString:(NSString *)string;
// Designated initializer. |string| will be copied and assigned to |_foo|.
- (id)initWithString:(NSString *)string;
// Does some work on |blah| and returns YES if the work was completed
// successfuly, and NO otherwise.
- (BOOL)doWorkWithString:(NSString *)blah;
@end
下例是一份源文件,展示对接口的@implementation 的实现的正确注释和留间隔。
//
// GTMFoo.m
// FooProject
//
// Created by Greg Miller on 6/13/08.
// Copyright 2008 Google, Inc. All rights reserved.
//
#import "GTMFoo.h"
@implementation GTMFoo
+ (id)fooWithString:(NSString *)string {
return [[self alloc] initWithString:string];
}
// Must always override super's designated initializer.
- (id)init {
return [self initWithString:nil];
}
- (id)initWithString:(NSString *)string {
if ((self = [super init])) {
_foo = string;
_bar = [[NSString alloc] initWithFormat:@"hi %d", 3];
}
return self;
}
- (BOOL)doWorkWithString:(NSString *)blah {
// ...
return NO;
}
@end
仅使用空格,缩进四个。
我们使用空格用于缩进,不要在编码时使用tab键,你应该设置你的编辑器将tab键转换成对应的空格。
代码中的每行文本不要超过80个字符的长度。
尽管Objective-C正变得比C++更加繁冗,为了保持规范的互通性,我们还是决定保持80字符长度的限制。这比你想象中的更容易做到。
我们知道本条款是有争议的,但已有此多的代码已经遵从了本条款,即使只是保持一致性也是一个充足的理由。
你可以在Xcode里清楚地发现代码中的违规,设置 Xcode > Preferences > Text Editing > Show page guide. (之后就可以在代码编辑区域里看到一条指定字符长度的指示线了)
留一个空格在-或+和返回类型之间,但参数列表里的参数之间不要留间隔。
方法应该写成这样:
- (void)doSomethingWithString:(NSString *)theString {
...
}
星号前的空格是可选的,你可以根据原来的代码风格自行决定。
如果参数过多,推荐每个参数各占一行。使用多行的情况下,以参数前的冒号用于对齐:
- (void)doSomethingWith:(GTMFoo *)theFoo
rect:(NSRect)theRect
interval:(float)theInterval {
...
}
当第一个关键字比其他的短时,后续行至少缩进四个空格。这样你可以让后续的关键字垂直对齐,而不是用冒号对齐:
- (void)short:(GTMFoo *)theFoo
longKeyword:(NSRect)theRect
evenLongerKeyword:(float)theInterval {
...
}
方法调用的格式和方法声明时的格式时一致的,如果格式风格可选,遵从原有代码的风格。
调用应该将所有参数写在一行:
[myObject doFooWith:arg1 name:arg2 error:arg3];
或者每个参数一行,用冒号对齐: (对齐效果如前说明)
[myObject doFooWith:arg1
name:arg2
error:arg3];
不要使用如下风格的写法
[myObject doFooWith:arg1 name:arg2 // some lines with >1 arg
error:arg3];
[myObject doFooWith:arg1
name:arg2 error:arg3];
[myObject doFooWith:arg1
name:arg2 // aligning keywords instead of colons
error:arg3];
在声明和定义时,如果因为关键字长度使就算有四个空格在前仍然无法用冒号对齐,那么就让后续行缩进四个空格而不是冒号对齐:
[myObj short:arg1
longKeyword:arg2
evenLongerKeyword:arg3];
权限控制符@public 和@private 缩进一个空格.
类似C++的public,protected,private:
@interface MyClass : NSObject {
@public
...
@private
...
}
@end
不要 @throw Objective-C的异常
命名规则对于维护代码来说是非常重要的。Objective-C方法名往往很长,不过这也有好处,读代码就像读散文(放屁),让很多注释变得毫无意义。
写纯Objective-C代码时,我们基本上遵守标准Objective-C naming rules ,这些规则和C++的规则有很大的不同。比如Google的C++代码规范推荐变量名构词之间使用下划线隔开,而本文档推荐驼峰法,也是Objective-C社区的标准。
所有类,类别, 方法,以及变量如包括缩写,则缩写部分使用全大写的缩写(Initialisms )形式。这遵守Apple的标准,比如URL,TIFF以及EXIF。
当写Objective-C++代码时,情况就不是那么单一了。许多项目需要实现带一些Objective-C代码的跨平台的C++APIs或者连接后台的C++代码与前台的原生Cocoa代码.这会造成两种规范直接冲突。
我们的解决方法是根据方法/函数风格来决定。如果在@implementation 块,就使用Objective-C的命名规则;如果在C++的方法实现块,就使用C++的命名规则。避免了实体变量和本地变量在一个函数内命名规则冲突的情况,而这种情况是对可读性的极大损害。
文件名反映了它所包含的实现类的名字,遵从你所在项目的习惯。
文件扩展名使用如下规则
| 后缀 | 说明 |
|---|---|
| .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 |
类别的文件名应该包含扩展类的名字,比如GTMNSString+Utils.h or GTMNSTextView+Autocomplete.h
方法使用小写开头的驼峰法命名,每个参数都应该小写开头。
方法名应该尽可能读起来像一句话,参数名就相对方法名的补充说明(比如convertPoing:fromRect: 或者 replaceCharactersInRange:withString: ),详见Apple's Guide to Naming Methods
存取(Accessor)方法应该一致的在"取(getting)"的时候直接用变量名而不是在签名加"get",如下:
- (id)getDelegate; // AVOID
- (id)delegate; // GOOD
不过这仅针对Objective-C代码,C++代码仍然遵循自己的代码规范。
变量名使用小写开头的驼峰法,类成员变量名前面加一个下划线,比如:myLovalVariable, _myInstanceVariable.
一般变量命名
不要使用匈牙利命名法去标记语法,比如静态类型或变量类型(int或pointer之类的)。使变量名尽量可以推测其用途属性具有描述性。别一心想着少打几个字母,让你的代码可以迅速被理解更加重要。比如:
// 避免
int w;
int nerr;
int nCompConns;
tix = [[NSMutableArray alloc] init];
obj = [someObject object];
p = [network port];
// GOOD
int numErrors;
int numCompletedConnections;
tickets = [[NSMutableArray alloc] init];
userInfo = [someObject object];
port = [network port];
实体变量
实体变量用驼峰法命名并前缀下划线,就像_usernameTextField. 然而我们允许一种例外就是用KVO/KVC去绑定一个实体变量而Objective-C 2.0 不能用(因为操作系统的限制)的情况,此时也可用前缀下划线的方法给每个变量命名。如果可以使用Objective-C 2.0,@property 和 @synthesize 提供了遵守命名规范的解决方法。
常量
常量(预定义,枚举,局部常量等)使用小写k开头的驼峰法,比如kInvalidHandle , kWritePerm .
注释并说明指定的初始化。
明确指定初始化对想要子类化你的类的时候时很重要的。那样,子类化时只需要做一个或多个初始化去保证初值即可。这也有助于在以后调试你的类时明了初始化流程。
当重写一个子类并需要init... 方法,注意要重写父类的指定初始化方法。
如果你没有正确重写父类的指定初始化方法,你的初始化方法可能不会被调用,这会导致很多微妙而难以排除的错误。
没必要在初始化方法里把变量初始化为0 或者nil ,这是多余的。
所有新分配内存的对象内容都初始化为0(除了 isa ),所以不要在init 方法里做无谓的重初始化为0的操作。
保持你的类简单,避免"厨房水槽"似的APIs,如果一个方法没必要公开就不要公开。使用私有类别保证公开头文件的简洁。
和C++不同,Objective-C无法区分公有私有方法,因为它全是公有的。因此,除非就是为了让用户调用所设计,不要把其他的方法放到公有API里。这样可以减少不期调用的可能性。这还包括重写父类的方法。对于那些内部实现的方法,在实现文件里使用类别而不是将方法定义在公有头文件里。
// GTMFoo.m
#import "GTMFoo.h"
@interface GTMFoo (PrivateDelegateHandling)
- (NSString *)doSomethingWithDelegate; // Declare private method
@end
@implementation GTMFoo(PrivateDelegateHandling)
...
- (NSString *)doSomethingWithDelegate {
// Implement this method
}
...
@end
在Objective-C 2.0之前,如果你在私有@interface 里声明了一个方法,但忘记在主@implementation 文件里实现了,编译器不会有什么反应(这是因为你没有在不同的类别里实现这些私有方法)。解决方案在是把函数写到@implementation 里并指明类别。
如果你用的是 Objective-C 2.0,你应该使用类扩展 而不是声明私有类别,如下:
@interface GMFoo () { ... }
如此就可以保证函数做了声明但没有在@implememtation 里实现的时候编译器会警报。
再者,"private"方法并不是真正的private,你可能会无意间重写了父类的一个"private"方法,这回导致bug的涌现。总的来说,私有方法应该使用更特别的名字以阻止子类化时并不期望的重写。
最后,对于绝大多数类而言,Objective-C的类别是将@implelemtation做可理解的分块,添加新的应用级别的功能的最佳途径。比如,与其在你的项目里随便找个类来实现字符串的"中间截断"功能,不如创建一个新的NSString 类别。
用#import 导入Objective-C或Objective-C++头文件,用#include 导入C或C++头文件
根据头文件的语言去选择合适的导入方式。
当导入的头文件使用Objective-C或Objective-C++语言时,使用#import . 当导入标准C或 C++头文件时,使用#include . 头文件应该使用自己的#define 重加载保护
有些Objective-C头文件没有#define 重加载保护,所以只应该用#import 导入。因此Objective-C头文件只应该被Objective-C源文件或其他的Objective-C头文件所导入。这种情况下全部使用#import 是合适的。
标准C和C++头文件不包含任何Objective-C元素都可以被一般的C或C++文件导入。因为标准C和C++里根本没有#import ,所以也只能用#include 导入。在Objective-C代码中使用#include 一致的导入这些头文件。
本条款有助于跨平台项目的无意错误。一位Mac开发者引入一份新C或C++头文件时可能会忘记添加#define 重加载保护,因为在Mac上用#import 导入文件不会引发问题,但在别的使用#include 的平台就可能出问题。在所有平台一致的使用#include 意味着要么全部成功要么全部失败,避免了那种另人沮丧的一些平台上可以运作而另一些不行的情况。
#import <Cocoa/Cocoa.h>
#include <CoreFoundation/CoreFoundation.h>
#import "GTMFoo.h"
#include "base/basictypes.h"
导入框架根的头文件而不是分别导入框架头文件
看起来从Cocoa或Foundation这些框架里导入个别的文件很不错,但实际上你直接导入框架根头文件效率更高。框架根已经被预编译故可更快的被加载。还有,记住用#import 指令而不是#include 导入Objective-C的框架。
#import <Foundation/Foundation.h> // good
#import <Foundation/NSArray.h> // 避免
#import <Foundation/NSString.h>
...
属性总的来说是遵循如下告诫的: 属性是Objective-C 2.0的特性,所以只能跑在iPhone以及MacOS X 10.5(leopard)或更高的版本。点表示法访问属性是不被允许的。
命名
一个有属性关联实例变量都要在前面加下划线,而该属性的名称就是实例变量不加下划线的名字。 注意在最新的Xcode中不需要手工关联实例变量。
@interface MyClass : NSObject
@property (copy, nonatomic) NSString *name;
@end
@implementation MyClass
@end
位置
在类接口声明里,属性的声明必须紧挨着实例变量块声明之后。类定义里,属性的定义必须紧挨着@implementation块。缩进和@interface或@implementation是一样的。
@interface MyClass : NSObject
@property (copy, nonatomic) NSString *name;
@end
@implementation MyClass
- (id)init {
...
}
@end
对字符串使用拷贝特性
NSString 类型的属性应该永远声明为带有copy attribute
这点是因为NSString 的setters 永远使用copy 而不是retain 。
永远不要对CFType属性使用synthesize
CFType类型应该永远遵守@dynamic的实现规则。
因为CFType类型不能有reatin 属性特性,开发者必须自己维护。(下面不懂,原文In the rare case that you do actually want assignment it is better to make that completely clear by actually implementing the setter and getter and commenting why that is the case. )
将模型与视图分离,将控制器从视图和模型中分离,回调APIs使用@protocol
模型与视图分离: 不要对模型或数据源的表现形式做任何假设。保持数据与表现层的接口的抽象性。模型不对视图有任何了解(一个好的实践就是询问你自己是否能对数据有多个不同形式的表现方法)
控制器从视图和模型中分离: 不要把"业务逻辑"放到视图相关的类;这会导致代码可复用性下降。让控制器的类承管代码,但不要对表现形式做太多假设。
用@protocol 定义回调APIs: 如果不是所有的方法都必须实现就使用@optional (例外:在Objecitve-C 1.0的时候,@optional 还不可用,所以用类别去定义一个"非标准协议(informal protocol)")