Objective-c编码规范

来源:互联网 发布:ff14 数据库 编辑:程序博客网 时间:2024/05/18 03:32

编码风格

代码缩进

不要在工程里使用Tab键,使用空格来进行缩进。在Xcode > Preferences > Text Editing将Tab和自动缩进都设置为4个空格。(Google的标准是使用两个空格来缩进,但这里还是推荐使用Xcode默认的设置。)

代码长度

在Xcode > Preferences > Text Editing > Page guide at column:中将最大行长设置为80,过长的一行代码将会导致可读性问题。

代码组织

在函数分组和protocol/delegate实现中使用#pragma mark -来分类方法,要遵循以下一般结构:

-列表内容

#pragma mark - Lifecycle
-(instancetype)init {}
-(void)dealloc {}
-(void)viewDidLoad {}
-(void)viewWillAppear:
-(BOOL)animated {}
-(void)didReceiveMemoryWarning {}

#pragma mark - Custom Accessors
-(void)setCustomProperty:(id)value {}
-(id)customProperty {}

#pragma mark - IBActions
-(IBAction)submitData:(id)sender {}

#pragma mark - Public
-(void)publicMethod {}

#pragma mark - Private
-(void)privateMethod {}

#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 {}

函数书写

典型的Objective-C函数应该是这样的:

-(void)writeVideoFrameWithData:(NSData *)frameData timeStamp:(int)timeStamp {

}

在-和(void)之间应该有一个空格,第一个大括号{的位置在函数所在行的末尾,同样应该有一个空格。(我司的C语言规范要求是第一个大括号单独占一行,但考虑到OC较长的函数名和苹果SDK代码的风格,还是将大括号放在行末。)
如果一个函数有特别多的参数或者名称很长,应该将其按照:来对齐分行显示:

-(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:(GTMFoo *)theFoo

    longKeyword:(NSRect)theRect

evenLongerKeyword:(float)theInterval
error:(NSError **)theError {

}

函数调用

函数调用的格式和书写差不多,可以按照函数的长短来选择写在一行或者分成多行:

//写在一行
[myObject doFooWith:arg1 name:arg2 error:arg3];

//分行写,按照’:’对齐
[myObject doFooWith:arg1
name:arg2
error:arg3];

//第一段名称过短的话后续可以进行缩进
[myObj short:arg1
longKeyword:arg2
evenLongerKeyword:arg3
error:arg4];

协议(Protocols)

在书写协议的时候注意用<>括起来的协议和类型名之间是没有空格的,比如IPCConnectHandler(),这个规则适用所有书写协议的地方,包括函数声明、类声明、实例变量等等:

@interface MyProtocoledClass : NSObject {
@private
id _delegate;
}

-(void)setDelegate:(id)aDelegate;
@end

在实现协议的时候尽量隐藏在实现类的扩张类和分类里面比如:

@interface BasicHouseDetailController ()
{

}
闭包(Blocks)

根据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) {
// …
}];
数据结构书写

应该使用可读性更好的语法糖来构造NSArray,NSDictionary等数据结构,避免使用冗长的alloc,init方法。[适用十分确定里面的值不为 nil];

如果构造代码写在一行,需要在括号两端留有一个空格,使得被构造的元素于与构造语法区分开来:

//正确,在语法糖的”[]”或者”{}”两端留有空格
NSArray *array = @[ [foo description], @”Another String”, [bar description] ];
NSDictionary *dict = @{ NSForegroundColorAttributeName : [NSColor redColor] };

2 .如果构造代码不写在一行内,构造元素需要使用两个空格来进行缩进,右括号]或者}写在新的一行,并且与调用语法糖那行代码的第一个非空字符对齐:

NSArray *array = @[
@”This”,
@”is”,
@”an”,
@”array”
];

NSDictionary *dictionary = @{
NSFontAttributeName : [NSFont fontWithName:@”Helvetica-Bold” size:12],
NSForegroundColorAttributeName : fontColor
};

构造字典时,字典的Key和Value与中间的冒号:都要留有一个空格,多行书写时,也可以将Value对齐:

//正确,冒号’:’前后留有一个空格
NSDictionary *option1 = @{
NSFontAttributeName : [NSFont fontWithName:@”Helvetica-Bold” size:12],
NSForegroundColorAttributeName : fontColor
};

//正确,按照Value来对齐
NSDictionary *option2 = @{
NSFontAttributeName : [NSFont fontWithName:@”Arial” size:12],
NSForegroundColorAttributeName : fontColor
};

在使用不确定是否存在的读写一定要使用安全方法

//NSArray && NSMutableArray
- (id)objectAtIndexSafely:(NSUInteger)index;
- (void)addObjectSafely:(id)anObject;
- (void)insertObjectSafely:(id)anObject atIndex:(NSUInteger)index;
- (void)removeObjectAtIndexSafely:(NSUInteger)index;
- (void)replaceObjectAtIndexSafely:(NSUInteger)index withObject:(id)anObject;

//NSDictionary && NSMutableDictionary
- (id)objectForKeySafely:(id)key;
- (void)removeObjectForKeySafely:(id)aKey;

-(void)setObjectSafely:(id)anObject forKey:(id )aKey;

BOOL的使用

BOOL在Objective-C中被定义为signed char类型,这意味着一个BOOL类型的变量不仅仅可以表示YES(1)和NO(0)两个值,所以永远不要将BOOL类型变量直接和YES比较:

//正确
BOOL great = [foo isGreat];
if (great) {
// …be great!
}

// 判断object 是否 nil
id someObject = [[NSObject alloc] init];
if (someObject) {}
if (![anotherObject boolValue]) {}

不要将其它类型的值作为BOOL来返回,这种情况下,BOOL变量只会取值的最后一个字节来赋值,这样很可能会取到0(NO)。但是,一些逻辑操作符比如&&,||,!的返回是可以直接赋给BOOL的:

//正确
- (BOOL)isBold {
return ([self fontTraits] & NSFontBoldTrait) ? YES : NO;
}

//正确,逻辑操作符可以直接转化为BOOL
- (BOOL)isValid {
return [self stringValue] != nil;
}
- (BOOL)isEnabled {
return [self isValid] && [self isBold];
}

枚举类型

当使用enum时,推荐使用新的固定基本类型规格,因为它有更强的类型检查和代码补全。现在SDK有一个宏NS_ENUM()来帮助和鼓励你使用固定的基本类型。

typedef NS_ENUM(NSInteger, RWTLeftMenuTopItemType) {
RWTLeftMenuTopItemMain,
RWTLeftMenuTopItemShows,
RWTLeftMenuTopItemSchedule
};

也可以指定值
typedef NS_ENUM(NSInteger, RWTGlobalConstants) {
RWTPinSizeMin = 1,
RWTPinSizeMax = 5,
RWTPinCountMin = 100,
RWTPinCountMax = 500,
};

Case语句

当一个case语句包含多行代码时,大括号应该加上。

switch (condition) {
case 1:

// …
break;
case 2: {

// …

// Multi-line example using braces
break;
}
case 3:

// …
break;
default:

// …
break;
}

有很多次,当相同代码被多个cases使用时,一个fall-through应该被使用。一个fall-through就是在case最后移除’break’语句,这样就能够允许执行流程跳转到下一个case值。为了代码更加清晰,一个fall-through需要注释一下。

switch (condition) {
case 1:

// * fall-through! *
case 2:

// code executed for values 1 and 2
break;
default:

// …
break;
}

当在switch使用枚举类型时,’default’是不需要的。例如

RWTLeftMenuTopItemType menuType = RWTLeftMenuTopItemMain;

switch (menuType) {
case RWTLeftMenuTopItemMain:

// …
break;
case RWTLeftMenuTopItemShows:

// …
break;
case RWTLeftMenuTopItemSchedule:

// …
break;
}
私有属性

私有属性应该在类的实现文件中的类扩展(匿名分类)中声明,命名分类(比如RWTPrivate或private)应该从不使用除非是扩展其他类。匿名分类应该通过使用+Private.h文件的命名规则暴露给测试。

@interface RWTDetailViewController ()

@property (strong, nonatomic) GADBannerView *googleAdView;
@property (strong, nonatomic) ADBannerView *iAdView;
@property (strong, nonatomic) UIWebView *adXWebView;

@end
条件语句

条件语句主体为了防止出错应该使用大括号包围,即使条件语句主体能够不用大括号编写(如,只用一行代码)。这些错误包括添加第二行代码和期望它成为if语句;还有,even more dangerous defect可能发生在if语句里面一行代码被注释了,然后下一行代码不知不觉地成为if语句的一部分。除此之外,这种风格与其他条件语句的风格保持一致,所以更加容易阅读。

if (!error) {
return success;
}
三元操作符

当需要提高代码的清晰性和简洁性时,三元操作符?:才会使用。单个条件求值常常需要它。多个条件求值时,如果使用if语句或重构成实例变量时,代码会更加易读。一般来说,最好使用三元操作符是在根据条件来赋值的情况下。

Non-boolean的变量与某东西比较,加上括号()会提高可读性。如果被比较的变量是boolean类型,那么就不需要括号。

NSInteger value = 5;
result = (value != 0) ? x : y;

BOOL isHorizontal = YES;
result = isHorizontal ? x : y;

Init方法

Init方法应该遵循Apple生成代码模板的命名规则。返回类型应该使用instancetype而不是id

-(instancetype)init {
self = [super init];
if (self) {

// …
}
return self;
}

类构造方法

@interface Airplane
+ (instancetype)airplaneWithType:(RWTAirplaneType)type;
@end

CGRect函数

当访问CGRect里的x, y, width, 或 height时,应该使用CGGeometry函数而不是直接通过结构体来访问。引用Apple的CGGeometry:

CGRect frame = self.view.frame;

CGFloat x = CGRectGetMinX(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);
CGRect frame = CGRectMake(0.0, 0.0, width, height);

ARC的使用

在init和dealloc中不要用存取方法访问实例变量
当initdealloc方法被执行时,类的运行时环境不是处于正常状态的,使用存取方法访问变量可能会导致不可预料的结果,因此应当在这两个方法内直接访问实例变量。

//正确,直接访问实例变量
- (instancetype)init {
self = [super init];
if (self) {
_bar = [[NSMutableString alloc] init];
}
return self;
}
- (void)dealloc {
[_bar release];
[super dealloc];
}

按照定义的顺序释放资源
在类或者Controller的生命周期结束时,往往需要做一些扫尾工作,比如释放资源,停止线程等,这些扫尾工作的释放顺序应当与它们的初始化或者定义的顺序保持一致。这样做是为了方便调试时寻找错误,也能防止遗漏。

保证NSString在赋值时被复制
NSString非常常用,在它被传递或者赋值时应当保证是以复制(copy)的方式进行的,这样可以防止在不知情的情况下String的值被其它对象修改。
-(void)setFoo:(NSString *)aFoo {
_foo = [aFoo copy];
}
nil检查

因为在Objective-C中向nil对象发送命令是不会抛出异常或者导致崩溃的,只是完全的“什么都不干”,所以,只在程序中使用nil来做逻辑上的检查。

另外,不要使用诸如nil == Object或者Object == nil的形式来判断。

//正确,直接判断
if (!objc) {

}

点分语法的使用

不要用点分语法来调用方法,只用来访问属性。这样是为了防止代码可读性问题。

//正确,使用点分语法访问属性
NSString *oldName = myObject.name;
myObject.name = @”Alice”;

Delegate要使用

一个类的Delegate对象通常还引用着类本身,这样很容易造成引用循环的问题,所以类的Delegate属性要设置为弱引用。

/* delegate /
@property (nonatomic, weak) id delegate;

黄金路径

当使用条件语句编码时,左手边的代码应该是”golden” 或 “happy”路径。也就是尽量不要嵌套if语句,多个返回语句也是OK。

-(void)someMethod {
if (![someOther boolValue]) {
return;
}

//Do something important
}
单例模式

-(instancetype)sharedInstance {
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});

return sharedInstance;
}

错误处理

当方法通过引用来返回一个错误参数,判断返回值而不是错误变量。

NSError *error;
if (![self trySomethingWithError:&error]) {

// Handle Error
}

常量

常量是容易重复被使用和无需通过查找和代替就能快速修改值。常量应该使用static来声明而不是使用#define,除非显式地使用宏。

static NSString * const RWTAboutViewControllerCompanyName = @”RayWenderlich.com”;

static CGFloat const RWTImageThumbnailHeight = 50.0;

属性使用

所有属性特性应该显式地列出来,有助于新手阅读代码。属性特性的顺序应该是storage、atomicity,与在Interface Builder连接UI元素时自动生成代码一致。

@property (weak, nonatomic) IBOutlet UIView *containerView;
@property (strong, nonatomic) NSString *tutorialName;
注释

读没有注释代码的痛苦你我都体会过,好的注释不仅能让人轻松读懂你的程序,还能提升代码的逼格。注意注释是为了让别人看懂,而不是仅仅你自己。

文件注释
每一个文件都必须写文件注释,文件注释通常包含

文件所在模块
作者信息
历史版本信息
版权信息
文件包含的内容,作用
一段良好文件注释

/*********************************************************************
Copyright (C), 2011-2013, Andrew Min Chang

File name:  AMCCommonLib.hAuthor:     Andrew Chang (Zhang Min) E-mail:     LaplaceZhang@126.comDescription:            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"    2012-08-20: Add shared memory library; add message queue.    2012-08-21: Add socket library (local)    2012-08-22: Add math library    2012-08-23: Add socket library (internet)    2012-08-24: Add daemon function    2012-10-10: Change file name as "AMCCommonLib.h"    2012-12-04: Add UDP support in AMC socket library    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.hCopyright information:         This file was intended to be under GPL protocol. However, I may use this library    in my work as I am an employee. And my company may require me to keep it secret.     Therefore, this file is neither open source nor under GPL control. 

**********************************************************************/

好的代码应该是“自解释”(self-documenting)的,但仍然需要详细的注释来说明参数的意义、返回值、功能以及可能的副作用。

方法、函数、类、协议、类别的定义都需要注释,推荐采用Apple的标准注释风格,好处是可以在引用的地方alt+点击自动弹出注释,非常方便。

有很多可以自动生成注释格式的插件,推荐使用VVDocumenter:

良好的注释:

/**
* Create a new preconnector to replace the old one with given mac address.
* NOTICE: We DO NOT stop the old preconnector, so handle it by yourself.
*
* @param type Connect type the preconnector use.
* @param macAddress Preconnector’s mac address.
*/
- (void)refreshConnectorWithConnectType:(IPCConnectType)type Mac:(NSString *)macAddress;

/**
* Stop current preconnecting when application is going to background.
*/
-(void)stopRunning;

/**
* Get the COPY of cloud device with a given mac address.
*
* @param macAddress Mac address of the device.
*
* @return Instance of IPCCloudDevice.
*/
-(IPCCloudDevice )getCloudDeviceWithMac:(NSString )macAddress;

// A delegate for NSApplication to handle notifications about app
// launch and shutdown. Owned by the main app controller.
@interface MyAppDelegate : NSObject {

}
@end
协议、委托的注释要明确说明其被触发的条件:

/* Delegate - Sent when failed to init connection, like p2p failed. /
-(void)initConnectionDidFailed:(IPCConnectHandler *)handler;
不要使用new方法

尽管很多时候能用new代替alloc init方法,但这可能会导致调试内存时出现不可预料的问题。Cocoa的规范就是使用alloc init方法,使用new会让一些读者困惑。

Public API要尽量简洁

共有接口要设计的简洁,满足核心的功能需求就可以了。不要设计很少会被用到,但是参数极其复杂的API。如果要定义复杂的方法,使用类别或者类扩展。

1 0
原创粉丝点击